/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later */

/** \file
 * \ingroup spseq
 */

#include "MEM_guardedalloc.h"

#include "BLI_fileops.h"
#include "BLI_math_vector.h"
#include "BLI_path_util.h"
#include "BLI_string.h"
#include "BLI_timecode.h"
#include "BLI_utildefines.h"

#include "BLT_translation.h"

#include "DNA_anim_types.h"
#include "DNA_scene_types.h"
#include "DNA_sound_types.h"

#include "BKE_context.hh"
#include "BKE_fcurve.h"
#include "BKE_global.h"
#include "BKE_main.hh"
#include "BKE_report.h"
#include "BKE_sound.h"

#include "SEQ_add.hh"
#include "SEQ_animation.hh"
#include "SEQ_channels.hh"
#include "SEQ_edit.hh"
#include "SEQ_effects.hh"
#include "SEQ_iterator.hh"
#include "SEQ_prefetch.hh"
#include "SEQ_relations.hh"
#include "SEQ_render.hh"
#include "SEQ_select.hh"
#include "SEQ_sequencer.hh"
#include "SEQ_time.hh"
#include "SEQ_transform.hh"
#include "SEQ_utils.hh"

#include "WM_api.hh"
#include "WM_types.hh"

#include "RNA_define.hh"
#include "RNA_enum_types.hh"
#include "RNA_prototypes.h"

/* For menu, popup, icons, etc. */
#include "ED_fileselect.hh"
#include "ED_numinput.hh"
#include "ED_scene.hh"
#include "ED_screen.hh"
#include "ED_sequencer.hh"

#include "UI_interface.hh"
#include "UI_resources.hh"
#include "UI_view2d.hh"

#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_build.hh"

/* Own include. */
#include "sequencer_intern.hh"

/* -------------------------------------------------------------------- */
/** \name Structs & Enums
 * \{ */

struct TransSeq {
  int start, machine;
  int startofs, endofs;
  int anim_startofs, anim_endofs;
  // int final_left, final_right; /* UNUSED. */
  int len;
  float content_start;
};

/** \} */

/* -------------------------------------------------------------------- */
/** \name Public Context Checks
 * \{ */

bool ED_space_sequencer_maskedit_mask_poll(bContext *C)
{
  return ED_space_sequencer_maskedit_poll(C);
}

bool ED_space_sequencer_check_show_maskedit(SpaceSeq *sseq, Scene *scene)
{
  if (sseq && sseq->mainb == SEQ_DRAW_IMG_IMBUF) {
    return (SEQ_active_mask_get(scene) != nullptr);
  }

  return false;
}

bool ED_space_sequencer_maskedit_poll(bContext *C)
{
  SpaceSeq *sseq = CTX_wm_space_seq(C);

  if (sseq) {
    Scene *scene = CTX_data_scene(C);
    return ED_space_sequencer_check_show_maskedit(sseq, scene);
  }

  return false;
}

bool ED_space_sequencer_check_show_imbuf(SpaceSeq *sseq)
{
  return (sseq->mainb == SEQ_DRAW_IMG_IMBUF) &&
         ELEM(sseq->view, SEQ_VIEW_PREVIEW, SEQ_VIEW_SEQUENCE_PREVIEW);
}

bool ED_space_sequencer_check_show_strip(SpaceSeq *sseq)
{
  return ELEM(sseq->view, SEQ_VIEW_SEQUENCE, SEQ_VIEW_SEQUENCE_PREVIEW);
}

static bool sequencer_fcurves_targets_color_strip(const FCurve *fcurve)
{
  if (!BLI_str_startswith(fcurve->rna_path, "sequence_editor.sequences_all[\"")) {
    return false;
  }

  if (!BLI_str_endswith(fcurve->rna_path, "\"].color")) {
    return false;
  }

  return true;
}

bool ED_space_sequencer_has_playback_animation(const SpaceSeq *sseq, const Scene *scene)
{
  if (sseq->draw_flag & SEQ_DRAW_BACKDROP) {
    return true;
  }

  if (!scene->adt) {
    return false;
  }
  if (!scene->adt->action) {
    return false;
  }

  LISTBASE_FOREACH (FCurve *, fcurve, &scene->adt->action->curves) {
    if (sequencer_fcurves_targets_color_strip(fcurve)) {
      return true;
    }
  }

  return false;
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Shared Poll Functions
 * \{ */

bool sequencer_edit_poll(bContext *C)
{
  return (SEQ_editing_get(CTX_data_scene(C)) != nullptr);
}

bool sequencer_edit_with_channel_region_poll(bContext *C)
{
  if (!sequencer_edit_poll(C)) {
    return false;
  }
  ARegion *region = CTX_wm_region(C);
  if (!(region && (region->regiontype == RGN_TYPE_CHANNELS))) {
    return false;
  }
  return true;
}

bool sequencer_editing_initialized_and_active(bContext *C)
{
  return ED_operator_sequencer_active(C) && sequencer_edit_poll(C);
}

#if 0 /* UNUSED */
bool sequencer_strip_poll(bContext *C)
{
  Editing *ed;
  return (((ed = SEQ_editing_get(CTX_data_scene(C))) != nullptr) && (ed->act_seq != nullptr));
}
#endif

bool sequencer_strip_editable_poll(bContext *C)
{
  Scene *scene = CTX_data_scene(C);
  if (ID_IS_LINKED(&scene->id)) {
    return false;
  }
  Editing *ed = SEQ_editing_get(scene);
  return (ed && (ed->act_seq != nullptr));
}

bool sequencer_strip_has_path_poll(bContext *C)
{
  Editing *ed;
  Sequence *seq;
  return (((ed = SEQ_editing_get(CTX_data_scene(C))) != nullptr) &&
          ((seq = ed->act_seq) != nullptr) && SEQ_HAS_PATH(seq));
}

bool sequencer_view_has_preview_poll(bContext *C)
{
  SpaceSeq *sseq = CTX_wm_space_seq(C);
  if (sseq == nullptr) {
    return false;
  }
  if (SEQ_editing_get(CTX_data_scene(C)) == nullptr) {
    return false;
  }
  if (!(ELEM(sseq->view, SEQ_VIEW_PREVIEW, SEQ_VIEW_SEQUENCE_PREVIEW) &&
        (sseq->mainb == SEQ_DRAW_IMG_IMBUF)))
  {
    return false;
  }
  ARegion *region = CTX_wm_region(C);
  if (!(region && region->regiontype == RGN_TYPE_PREVIEW)) {
    return false;
  }

  return true;
}

bool sequencer_view_preview_only_poll(const bContext *C)
{
  SpaceSeq *sseq = CTX_wm_space_seq(C);
  if (sseq == nullptr) {
    return false;
  }
  if (SEQ_editing_get(CTX_data_scene(C)) == nullptr) {
    return false;
  }
  if (!(ELEM(sseq->view, SEQ_VIEW_PREVIEW) && (sseq->mainb == SEQ_DRAW_IMG_IMBUF))) {
    return false;
  }
  ARegion *region = CTX_wm_region(C);
  if (!(region && region->regiontype == RGN_TYPE_PREVIEW)) {
    return false;
  }

  return true;
}

bool sequencer_view_strips_poll(bContext *C)
{
  SpaceSeq *sseq = CTX_wm_space_seq(C);
  if (sseq == nullptr) {
    return false;
  }
  if (!ED_space_sequencer_check_show_strip(sseq)) {
    return false;
  }
  ARegion *region = CTX_wm_region(C);
  if (!(region && region->regiontype == RGN_TYPE_WINDOW)) {
    return false;
  }
  return true;
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Remove Gaps Operator
 * \{ */

static int sequencer_gap_remove_exec(bContext *C, wmOperator *op)
{
  Scene *scene = CTX_data_scene(C);
  const bool do_all = RNA_boolean_get(op->ptr, "all");
  const Editing *ed = SEQ_editing_get(scene);

  SEQ_edit_remove_gaps(scene, ed->seqbasep, scene->r.cfra, do_all);

  WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
  DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);

  return OPERATOR_FINISHED;
}

void SEQUENCER_OT_gap_remove(wmOperatorType *ot)
{
  /* Identifiers. */
  ot->name = "Remove Gaps";
  ot->idname = "SEQUENCER_OT_gap_remove";
  ot->description =
      "Remove gap at current frame to first strip at the right, independent of selection or "
      "locked state of strips";

  /* Api callbacks. */
  //  ot->invoke = sequencer_snap_invoke;
  ot->exec = sequencer_gap_remove_exec;
  ot->poll = sequencer_edit_poll;

  /* Flags. */
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

  RNA_def_boolean(ot->srna, "all", false, "All Gaps", "Do all gaps to right of current frame");
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Insert Gaps Operator
 * \{ */

static int sequencer_gap_insert_exec(bContext *C, wmOperator *op)
{
  Scene *scene = CTX_data_scene(C);
  const int frames = RNA_int_get(op->ptr, "frames");
  const Editing *ed = SEQ_editing_get(scene);
  SEQ_transform_offset_after_frame(scene, ed->seqbasep, frames, scene->r.cfra);

  WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);

  return OPERATOR_FINISHED;
}

void SEQUENCER_OT_gap_insert(wmOperatorType *ot)
{
  /* Identifiers. */
  ot->name = "Insert Gaps";
  ot->idname = "SEQUENCER_OT_gap_insert";
  ot->description =
      "Insert gap at current frame to first strips at the right, independent of selection or "
      "locked state of strips";

  /* Api callbacks. */
  //  ot->invoke = sequencer_snap_invoke;
  ot->exec = sequencer_gap_insert_exec;
  ot->poll = sequencer_edit_poll;

  /* Flags. */
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

  RNA_def_int(ot->srna,
              "frames",
              10,
              0,
              INT_MAX,
              "Frames",
              "Frames to insert after current strip",
              0,
              1000);
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Snap Strips to the Current Frame Operator
 * \{ */

static int sequencer_snap_exec(bContext *C, wmOperator *op)
{
  Scene *scene = CTX_data_scene(C);

  Editing *ed = SEQ_editing_get(scene);
  ListBase *channels = SEQ_channels_displayed_get(ed);
  int snap_frame;

  snap_frame = RNA_int_get(op->ptr, "frame");

  /* Check meta-strips. */
  LISTBASE_FOREACH (Sequence *, seq, ed->seqbasep) {
    if (seq->flag & SELECT && !SEQ_transform_is_locked(channels, seq) &&
        SEQ_transform_sequence_can_be_translated(seq))
    {
      if ((seq->flag & (SEQ_LEFTSEL + SEQ_RIGHTSEL)) == 0) {
        SEQ_transform_translate_sequence(scene, seq, (snap_frame - seq->startofs) - seq->start);
      }
      else {
        if (seq->flag & SEQ_LEFTSEL) {
          SEQ_time_left_handle_frame_set(scene, seq, snap_frame);
        }
        else { /* SEQ_RIGHTSEL */
          SEQ_time_right_handle_frame_set(scene, seq, snap_frame);
        }
      }

      SEQ_relations_invalidate_cache_composite(scene, seq);
    }
  }

  /* Test for effects and overlap. */
  LISTBASE_FOREACH (Sequence *, seq, ed->seqbasep) {
    if (seq->flag & SELECT && !SEQ_transform_is_locked(channels, seq)) {
      seq->flag &= ~SEQ_OVERLAP;
      if (SEQ_transform_test_overlap(scene, ed->seqbasep, seq)) {
        SEQ_transform_seqbase_shuffle(ed->seqbasep, seq, scene);
      }
    }
  }

  /* Recalculate bounds of effect strips, offsetting the keyframes if not snapping any handle. */
  LISTBASE_FOREACH (Sequence *, seq, ed->seqbasep) {
    if (seq->type & SEQ_TYPE_EFFECT) {
      const bool either_handle_selected = (seq->flag & (SEQ_LEFTSEL | SEQ_RIGHTSEL)) != 0;

      if (seq->seq1 && (seq->seq1->flag & SELECT)) {
        if (!either_handle_selected) {
          SEQ_offset_animdata(
              scene, seq, (snap_frame - SEQ_time_left_handle_frame_get(scene, seq)));
        }
      }
      else if (seq->seq2 && (seq->seq2->flag & SELECT)) {
        if (!either_handle_selected) {
          SEQ_offset_animdata(
              scene, seq, (snap_frame - SEQ_time_left_handle_frame_get(scene, seq)));
        }
      }
      else if (seq->seq3 && (seq->seq3->flag & SELECT)) {
        if (!either_handle_selected) {
          SEQ_offset_animdata(
              scene, seq, (snap_frame - SEQ_time_left_handle_frame_get(scene, seq)));
        }
      }
    }
  }

  DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
  WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);

  return OPERATOR_FINISHED;
}

static int sequencer_snap_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
{
  Scene *scene = CTX_data_scene(C);

  int snap_frame;

  snap_frame = scene->r.cfra;

  RNA_int_set(op->ptr, "frame", snap_frame);
  return sequencer_snap_exec(C, op);
}

void SEQUENCER_OT_snap(wmOperatorType *ot)
{
  /* Identifiers. */
  ot->name = "Snap Strips to the Current Frame";
  ot->idname = "SEQUENCER_OT_snap";
  ot->description = "Frame where selected strips will be snapped";

  /* Api callbacks. */
  ot->invoke = sequencer_snap_invoke;
  ot->exec = sequencer_snap_exec;
  ot->poll = sequencer_edit_poll;

  /* Flags. */
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

  RNA_def_int(ot->srna,
              "frame",
              0,
              INT_MIN,
              INT_MAX,
              "Frame",
              "Frame where selected strips will be snapped",
              INT_MIN,
              INT_MAX);
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Trim Strips Operator
 * \{ */

struct SlipData {
  float init_mouseloc[2];
  int previous_offset;
  Sequence **seq_array;
  int num_seq;
  bool slow;
  int slow_offset; /* Offset at the point where offset was turned on. */
  NumInput num_input;
};

static void slip_add_sequences(ListBase *seqbasep, Sequence **seq_array)
{
  int i = 0;

  LISTBASE_FOREACH (Sequence *, seq, seqbasep) {
    if (!(seq->type & SEQ_TYPE_EFFECT) && (seq->flag & SELECT)) {
      seq_array[i] = seq;
      i++;
    }
  }
}

static int slip_count_sequences(ListBase *seqbasep)
{
  int trimmed_sequences = 0;

  LISTBASE_FOREACH (Sequence *, seq, seqbasep) {
    if (!(seq->type & SEQ_TYPE_EFFECT) && (seq->flag & SELECT)) {
      trimmed_sequences++;
    }
  }

  return trimmed_sequences;
}

static int sequencer_slip_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
  SlipData *data;
  Scene *scene = CTX_data_scene(C);
  Editing *ed = SEQ_editing_get(scene);
  float mouseloc[2];
  int num_seq;
  View2D *v2d = UI_view2d_fromcontext(C);

  /* Count the amount of elements to trim. */
  num_seq = slip_count_sequences(ed->seqbasep);

  if (num_seq == 0) {
    return OPERATOR_CANCELLED;
  }

  data = MEM_cnew<SlipData>("trimdata");
  op->customdata = static_cast<void *>(data);
  data->seq_array = MEM_cnew_array<Sequence *>(num_seq, "trimdata_sequences");
  data->num_seq = num_seq;
  data->previous_offset = 0;

  initNumInput(&data->num_input);
  data->num_input.idx_max = 0;
  data->num_input.val_flag[0] |= NUM_NO_FRACTION;
  data->num_input.unit_sys = USER_UNIT_NONE;
  data->num_input.unit_type[0] = 0;

  slip_add_sequences(ed->seqbasep, data->seq_array);

  UI_view2d_region_to_view(v2d, event->mval[0], event->mval[1], &mouseloc[0], &mouseloc[1]);

  copy_v2_v2(data->init_mouseloc, mouseloc);

  data->slow = false;

  WM_event_add_modal_handler(C, op);

  /* Notify so we draw extensions immediately. */
  WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);

  return OPERATOR_RUNNING_MODAL;
}

static void sequencer_slip_strips(Scene *scene, SlipData *data, int delta)
{
  for (int i = data->num_seq - 1; i >= 0; i--) {
    Sequence *seq = data->seq_array[i];

    ListBase *channels = SEQ_channels_displayed_get(SEQ_editing_get(scene));
    if (SEQ_transform_is_locked(channels, seq)) {
      continue;
    }

    SEQ_time_slip_strip(scene, seq, delta);
  }

  for (int i = data->num_seq - 1; i >= 0; i--) {
    Sequence *seq = data->seq_array[i];
    SEQ_relations_invalidate_cache_preprocessed(scene, seq);
  }
}

/* Make sure, that each strip contains at least 1 frame of content.
 * Returns clamped offset relative to current strip positions. */
static int sequencer_slip_apply_limits(const Scene *scene, SlipData *data, int *offset)
{
  int delta_offset = *offset - data->previous_offset;

  for (int i = 0; i < data->num_seq; i++) {
    Sequence *seq = data->seq_array[i];
    int seq_content_start = SEQ_time_start_frame_get(seq) + delta_offset;
    int seq_content_end = seq_content_start + seq->len + seq->anim_startofs + seq->anim_endofs;
    int diff = 0;

    if (seq_content_start >= SEQ_time_right_handle_frame_get(scene, seq)) {
      diff = SEQ_time_right_handle_frame_get(scene, seq) - seq_content_start - 1;
    }

    if (seq_content_end <= SEQ_time_left_handle_frame_get(scene, seq)) {
      diff = SEQ_time_left_handle_frame_get(scene, seq) - seq_content_end + 1;
    }
    *offset += diff;
    delta_offset += diff;
  }
  data->previous_offset = *offset;

  return delta_offset;
}

static int sequencer_slip_exec(bContext *C, wmOperator *op)
{
  Scene *scene = CTX_data_scene(C);
  Editing *ed = SEQ_editing_get(scene);
  int offset = RNA_int_get(op->ptr, "offset");

  /* Count the amount of elements to trim. */
  int num_seq = slip_count_sequences(ed->seqbasep);

  if (num_seq == 0) {
    return OPERATOR_CANCELLED;
  }

  SlipData *data = MEM_cnew<SlipData>("trimdata");
  op->customdata = static_cast<void *>(data);
  data->seq_array = MEM_cnew_array<Sequence *>(num_seq, "trimdata_sequences");
  data->num_seq = num_seq;

  slip_add_sequences(ed->seqbasep, data->seq_array);

  sequencer_slip_apply_limits(scene, data, &offset);
  sequencer_slip_strips(scene, data, offset);

  MEM_freeN(data->seq_array);
  MEM_freeN(data);

  WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
  DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
  return OPERATOR_FINISHED;
}

static void sequencer_slip_update_header(Scene *scene, ScrArea *area, SlipData *data, int offset)
{
  char msg[UI_MAX_DRAW_STR];

  if (area) {
    if (hasNumInput(&data->num_input)) {
      char num_str[NUM_STR_REP_LEN];
      outputNumInput(&data->num_input, num_str, &scene->unit);
      SNPRINTF(msg, RPT_("Slip offset: %s"), num_str);
    }
    else {
      SNPRINTF(msg, RPT_("Slip offset: %d"), offset);
    }
  }

  ED_area_status_text(area, msg);
}

static int sequencer_slip_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
  Scene *scene = CTX_data_scene(C);
  SlipData *data = (SlipData *)op->customdata;
  ScrArea *area = CTX_wm_area(C);
  const bool has_numInput = hasNumInput(&data->num_input);
  bool handled = true;

  /* Modal numinput active, try to handle numeric inputs. */
  if (event->val == KM_PRESS && has_numInput && handleNumInput(C, &data->num_input, event)) {
    float offset_fl;
    applyNumInput(&data->num_input, &offset_fl);
    int offset = round_fl_to_int(offset_fl);

    const int delta_offset = sequencer_slip_apply_limits(scene, data, &offset);
    sequencer_slip_update_header(scene, area, data, offset);

    RNA_int_set(op->ptr, "offset", offset);

    sequencer_slip_strips(scene, data, delta_offset);

    WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);

    return OPERATOR_RUNNING_MODAL;
  }

  switch (event->type) {
    case MOUSEMOVE: {
      if (!has_numInput) {
        float mouseloc[2];
        int offset;
        int mouse_x;
        View2D *v2d = UI_view2d_fromcontext(C);

        if (data->slow) {
          mouse_x = event->mval[0] - data->slow_offset;
          mouse_x *= 0.1f;
          mouse_x += data->slow_offset;
        }
        else {
          mouse_x = event->mval[0];
        }

        /* Choose the side based on which side of the current frame the mouse is. */
        UI_view2d_region_to_view(v2d, mouse_x, 0, &mouseloc[0], &mouseloc[1]);
        offset = mouseloc[0] - data->init_mouseloc[0];

        const int delta_offset = sequencer_slip_apply_limits(scene, data, &offset);
        sequencer_slip_update_header(scene, area, data, offset);

        RNA_int_set(op->ptr, "offset", offset);

        sequencer_slip_strips(scene, data, delta_offset);

        WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
      }
      break;
    }

    case LEFTMOUSE:
    case EVT_RETKEY:
    case EVT_SPACEKEY: {
      MEM_freeN(data->seq_array);
      MEM_freeN(data);
      op->customdata = nullptr;
      if (area) {
        ED_area_status_text(area, nullptr);
      }
      DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
      WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
      return OPERATOR_FINISHED;
    }

    case EVT_ESCKEY:
    case RIGHTMOUSE: {
      int offset = RNA_int_get(op->ptr, "offset");
      sequencer_slip_strips(scene, data, -offset);

      MEM_freeN(data->seq_array);
      MEM_freeN(data);
      op->customdata = nullptr;

      WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);

      if (area) {
        ED_area_status_text(area, nullptr);
      }

      return OPERATOR_CANCELLED;
    }

    case EVT_RIGHTSHIFTKEY:
    case EVT_LEFTSHIFTKEY:
      if (!has_numInput) {
        if (event->val == KM_PRESS) {
          data->slow = true;
          data->slow_offset = event->mval[0];
        }
        else if (event->val == KM_RELEASE) {
          data->slow = false;
        }
      }
      break;

    default:
      handled = false;
      break;
  }

  /* Modal numinput inactive, try to handle numeric inputs. */
  if (!handled && event->val == KM_PRESS && handleNumInput(C, &data->num_input, event)) {
    float offset_fl;
    applyNumInput(&data->num_input, &offset_fl);
    int offset = round_fl_to_int(offset_fl);

    const int delta_offset = sequencer_slip_apply_limits(scene, data, &offset);
    sequencer_slip_update_header(scene, area, data, offset);

    RNA_int_set(op->ptr, "offset", offset);

    sequencer_slip_strips(scene, data, delta_offset);

    WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
  }

  return OPERATOR_RUNNING_MODAL;
}

void SEQUENCER_OT_slip(wmOperatorType *ot)
{
  /* Identifiers. */
  ot->name = "Slip Strips";
  ot->idname = "SEQUENCER_OT_slip";
  ot->description = "Slip the contents of selected strips";

  /* Api callbacks. */
  ot->invoke = sequencer_slip_invoke;
  ot->modal = sequencer_slip_modal;
  ot->exec = sequencer_slip_exec;
  ot->poll = sequencer_edit_poll;

  /* Flags. */
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

  RNA_def_int(ot->srna,
              "offset",
              0,
              INT32_MIN,
              INT32_MAX,
              "Offset",
              "Offset to the data of the strip",
              INT32_MIN,
              INT32_MAX);
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Mute Strips Operator
 * \{ */

static int sequencer_mute_exec(bContext *C, wmOperator *op)
{
  Scene *scene = CTX_data_scene(C);
  Editing *ed = SEQ_editing_get(scene);
  ListBase *channels = SEQ_channels_displayed_get(ed);

  bool selected;

  selected = !RNA_boolean_get(op->ptr, "unselected");

  LISTBASE_FOREACH (Sequence *, seq, ed->seqbasep) {
    if (!SEQ_transform_is_locked(channels, seq)) {
      if (selected) {
        if (seq->flag & SELECT) {
          seq->flag |= SEQ_MUTE;
          SEQ_relations_invalidate_dependent(scene, seq);
        }
      }
      else {
        if ((seq->flag & SELECT) == 0) {
          seq->flag |= SEQ_MUTE;
          SEQ_relations_invalidate_dependent(scene, seq);
        }
      }
    }
  }

  DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
  WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);

  return OPERATOR_FINISHED;
}

void SEQUENCER_OT_mute(wmOperatorType *ot)
{
  /* Identifiers. */
  ot->name = "Mute Strips";
  ot->idname = "SEQUENCER_OT_mute";
  ot->description = "Mute (un)selected strips";

  /* Api callbacks. */
  ot->exec = sequencer_mute_exec;
  ot->poll = sequencer_edit_poll;

  /* Flags. */
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

  RNA_def_boolean(
      ot->srna, "unselected", false, "Unselected", "Mute unselected rather than selected strips");
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Unmute Strips Operator
 * \{ */

static int sequencer_unmute_exec(bContext *C, wmOperator *op)
{
  Scene *scene = CTX_data_scene(C);
  Editing *ed = SEQ_editing_get(scene);
  ListBase *channels = SEQ_channels_displayed_get(ed);
  bool selected;

  selected = !RNA_boolean_get(op->ptr, "unselected");

  LISTBASE_FOREACH (Sequence *, seq, ed->seqbasep) {
    if (!SEQ_transform_is_locked(channels, seq)) {
      if (selected) {
        if (seq->flag & SELECT) {
          seq->flag &= ~SEQ_MUTE;
          SEQ_relations_invalidate_dependent(scene, seq);
        }
      }
      else {
        if ((seq->flag & SELECT) == 0) {
          seq->flag &= ~SEQ_MUTE;
          SEQ_relations_invalidate_dependent(scene, seq);
        }
      }
    }
  }

  DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
  WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);

  return OPERATOR_FINISHED;
}

void SEQUENCER_OT_unmute(wmOperatorType *ot)
{
  /* Identifiers. */
  ot->name = "Unmute Strips";
  ot->idname = "SEQUENCER_OT_unmute";
  ot->description = "Unmute (un)selected strips";

  /* Api callbacks. */
  ot->exec = sequencer_unmute_exec;
  ot->poll = sequencer_edit_poll;

  /* Flags. */
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

  RNA_def_boolean(ot->srna,
                  "unselected",
                  false,
                  "Unselected",
                  "Unmute unselected rather than selected strips");
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Lock Strips Operator
 * \{ */

static int sequencer_lock_exec(bContext *C, wmOperator * /*op*/)
{
  Scene *scene = CTX_data_scene(C);
  Editing *ed = SEQ_editing_get(scene);

  LISTBASE_FOREACH (Sequence *, seq, ed->seqbasep) {
    if (seq->flag & SELECT) {
      seq->flag |= SEQ_LOCK;
    }
  }

  WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);

  return OPERATOR_FINISHED;
}

void SEQUENCER_OT_lock(wmOperatorType *ot)
{
  /* Identifiers. */
  ot->name = "Lock Strips";
  ot->idname = "SEQUENCER_OT_lock";
  ot->description = "Lock strips so they can't be transformed";

  /* Api callbacks. */
  ot->exec = sequencer_lock_exec;
  ot->poll = sequencer_edit_poll;

  /* Flags. */
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Unlock Strips Operator
 * \{ */

static int sequencer_unlock_exec(bContext *C, wmOperator * /*op*/)
{
  Scene *scene = CTX_data_scene(C);
  Editing *ed = SEQ_editing_get(scene);

  LISTBASE_FOREACH (Sequence *, seq, ed->seqbasep) {
    if (seq->flag & SELECT) {
      seq->flag &= ~SEQ_LOCK;
    }
  }

  WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);

  return OPERATOR_FINISHED;
}

void SEQUENCER_OT_unlock(wmOperatorType *ot)
{
  /* Identifiers. */
  ot->name = "Unlock Strips";
  ot->idname = "SEQUENCER_OT_unlock";
  ot->description = "Unlock strips so they can be transformed";

  /* Api callbacks. */
  ot->exec = sequencer_unlock_exec;
  ot->poll = sequencer_edit_poll;

  /* Flags. */
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Reload Strips Operator
 * \{ */

static int sequencer_reload_exec(bContext *C, wmOperator *op)
{
  Main *bmain = CTX_data_main(C);
  Scene *scene = CTX_data_scene(C);
  Editing *ed = SEQ_editing_get(scene);
  const bool adjust_length = RNA_boolean_get(op->ptr, "adjust_length");

  LISTBASE_FOREACH (Sequence *, seq, ed->seqbasep) {
    if (seq->flag & SELECT) {
      SEQ_add_reload_new_file(bmain, scene, seq, !adjust_length);

      if (adjust_length) {
        if (SEQ_transform_test_overlap(scene, ed->seqbasep, seq)) {
          SEQ_transform_seqbase_shuffle(ed->seqbasep, seq, scene);
        }
      }
    }
  }

  WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);

  return OPERATOR_FINISHED;
}

void SEQUENCER_OT_reload(wmOperatorType *ot)
{
  PropertyRNA *prop;

  /* Identifiers. */
  ot->name = "Reload Strips";
  ot->idname = "SEQUENCER_OT_reload";
  ot->description = "Reload strips in the sequencer";

  /* Api callbacks. */
  ot->exec = sequencer_reload_exec;
  ot->poll = sequencer_edit_poll;

  /* Flags. */
  ot->flag = OPTYPE_REGISTER; /* No undo, the data changed is stored outside 'main'. */

  prop = RNA_def_boolean(ot->srna,
                         "adjust_length",
                         false,
                         "Adjust Length",
                         "Adjust length of strips to their data length");
  RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Refresh Sequencer Operator
 * \{ */

static bool sequencer_refresh_all_poll(bContext *C)
{
  if (G.is_rendering) {
    return false;
  }
  return sequencer_edit_poll(C);
}

static int sequencer_refresh_all_exec(bContext *C, wmOperator * /*op*/)
{
  Scene *scene = CTX_data_scene(C);
  Editing *ed = SEQ_editing_get(scene);

  SEQ_relations_free_imbuf(scene, &ed->seqbase, false);

  WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);

  return OPERATOR_FINISHED;
}

void SEQUENCER_OT_refresh_all(wmOperatorType *ot)
{
  /* Identifiers. */
  ot->name = "Refresh Sequencer";
  ot->idname = "SEQUENCER_OT_refresh_all";
  ot->description = "Refresh the sequencer editor";

  /* Api callbacks. */
  ot->exec = sequencer_refresh_all_exec;
  ot->poll = sequencer_refresh_all_poll;
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Reassign Inputs Operator
 * \{ */

int seq_effect_find_selected(Scene *scene,
                             Sequence *activeseq,
                             int type,
                             Sequence **r_selseq1,
                             Sequence **r_selseq2,
                             Sequence **r_selseq3,
                             const char **r_error_str)
{
  Editing *ed = SEQ_editing_get(scene);
  Sequence *seq1 = nullptr, *seq2 = nullptr, *seq3 = nullptr;

  *r_error_str = nullptr;

  if (!activeseq) {
    seq2 = SEQ_select_active_get(scene);
  }

  LISTBASE_FOREACH (Sequence *, seq, ed->seqbasep) {
    if (seq->flag & SELECT) {
      if (seq->type == SEQ_TYPE_SOUND_RAM && SEQ_effect_get_num_inputs(type) != 0) {
        *r_error_str = N_("Cannot apply effects to audio sequence strips");
        return 0;
      }
      if (!ELEM(seq, activeseq, seq2)) {
        if (seq2 == nullptr) {
          seq2 = seq;
        }
        else if (seq1 == nullptr) {
          seq1 = seq;
        }
        else if (seq3 == nullptr) {
          seq3 = seq;
        }
        else {
          *r_error_str = N_("Cannot apply effect to more than 3 sequence strips");
          return 0;
        }
      }
    }
  }

  /* Make sequence selection a little bit more intuitive
   * for 3 strips: the last-strip should be seq3. */
  if (seq3 != nullptr && seq2 != nullptr) {
    Sequence *tmp = seq2;
    seq2 = seq3;
    seq3 = tmp;
  }

  switch (SEQ_effect_get_num_inputs(type)) {
    case 0:
      *r_selseq1 = *r_selseq2 = *r_selseq3 = nullptr;
      return 1; /* Success. */
    case 1:
      if (seq2 == nullptr) {
        *r_error_str = N_("At least one selected sequence strip is needed");
        return 0;
      }
      if (seq1 == nullptr) {
        seq1 = seq2;
      }
      if (seq3 == nullptr) {
        seq3 = seq2;
      }
      ATTR_FALLTHROUGH;
    case 2:
      if (seq1 == nullptr || seq2 == nullptr) {
        *r_error_str = N_("2 selected sequence strips are needed");
        return 0;
      }
      if (seq3 == nullptr) {
        seq3 = seq2;
      }
      break;
  }

  if (seq1 == nullptr && seq2 == nullptr && seq3 == nullptr) {
    *r_error_str = N_("TODO: in what cases does this happen?");
    return 0;
  }

  *r_selseq1 = seq1;
  *r_selseq2 = seq2;
  *r_selseq3 = seq3;

  /* TODO(Richard): This function needs some refactoring, this is just quick hack for #73828. */
  if (SEQ_effect_get_num_inputs(type) < 3) {
    *r_selseq3 = nullptr;
  }
  if (SEQ_effect_get_num_inputs(type) < 2) {
    *r_selseq2 = nullptr;
  }

  return 1;
}

static int sequencer_reassign_inputs_exec(bContext *C, wmOperator *op)
{
  Scene *scene = CTX_data_scene(C);
  Sequence *seq1, *seq2, *seq3, *last_seq = SEQ_select_active_get(scene);
  const char *error_msg;

  if (SEQ_effect_get_num_inputs(last_seq->type) == 0) {
    BKE_report(op->reports, RPT_ERROR, "Cannot reassign inputs: strip has no inputs");
    return OPERATOR_CANCELLED;
  }

  if (!seq_effect_find_selected(
          scene, last_seq, last_seq->type, &seq1, &seq2, &seq3, &error_msg) ||
      SEQ_effect_get_num_inputs(last_seq->type) == 0)
  {
    BKE_report(op->reports, RPT_ERROR, error_msg);
    return OPERATOR_CANCELLED;
  }
  /* Check if reassigning would create recursivity. */
  if (SEQ_relations_render_loop_check(seq1, last_seq) ||
      SEQ_relations_render_loop_check(seq2, last_seq) ||
      SEQ_relations_render_loop_check(seq3, last_seq))
  {
    BKE_report(op->reports, RPT_ERROR, "Cannot reassign inputs: recursion detected");
    return OPERATOR_CANCELLED;
  }

  last_seq->seq1 = seq1;
  last_seq->seq2 = seq2;
  last_seq->seq3 = seq3;

  int old_start = last_seq->start;

  /* Force time position update for reassigned effects.
   * TODO(Richard): This is because internally startdisp is still used, due to poor performance of
   * mapping effect range to inputs. This mapping could be cached though. */
  SEQ_sequence_lookup_tag(scene, SEQ_LOOKUP_TAG_INVALID);
  SEQ_time_left_handle_frame_set(scene, seq1, SEQ_time_left_handle_frame_get(scene, seq1));

  SEQ_relations_invalidate_cache_preprocessed(scene, last_seq);
  SEQ_offset_animdata(scene, last_seq, (last_seq->start - old_start));

  WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);

  return OPERATOR_FINISHED;
}

static bool sequencer_effect_poll(bContext *C)
{
  Scene *scene = CTX_data_scene(C);
  Editing *ed = SEQ_editing_get(scene);

  if (ed) {
    Sequence *last_seq = SEQ_select_active_get(scene);
    if (last_seq && (last_seq->type & SEQ_TYPE_EFFECT)) {
      return true;
    }
  }

  return false;
}

void SEQUENCER_OT_reassign_inputs(wmOperatorType *ot)
{
  /* Identifiers. */
  ot->name = "Reassign Inputs";
  ot->idname = "SEQUENCER_OT_reassign_inputs";
  ot->description = "Reassign the inputs for the effect strip";

  /* Api callbacks. */
  ot->exec = sequencer_reassign_inputs_exec;
  ot->poll = sequencer_effect_poll;

  /* Flags. */
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Swap Inputs Operator
 * \{ */

static int sequencer_swap_inputs_exec(bContext *C, wmOperator *op)
{
  Scene *scene = CTX_data_scene(C);
  Sequence *seq, *last_seq = SEQ_select_active_get(scene);

  if (last_seq->seq1 == nullptr || last_seq->seq2 == nullptr) {
    BKE_report(op->reports, RPT_ERROR, "No valid inputs to swap");
    return OPERATOR_CANCELLED;
  }

  seq = last_seq->seq1;
  last_seq->seq1 = last_seq->seq2;
  last_seq->seq2 = seq;

  SEQ_relations_invalidate_cache_preprocessed(scene, last_seq);

  WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);

  return OPERATOR_FINISHED;
}
void SEQUENCER_OT_swap_inputs(wmOperatorType *ot)
{
  /* Identifiers. */
  ot->name = "Swap Inputs";
  ot->idname = "SEQUENCER_OT_swap_inputs";
  ot->description = "Swap the first two inputs for the effect strip";

  /* Api callbacks. */
  ot->exec = sequencer_swap_inputs_exec;
  ot->poll = sequencer_effect_poll;

  /* Flags. */
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Split Strips Operator
 * \{ */

static int mouse_frame_side(View2D *v2d, short mouse_x, int frame)
{
  int mval[2];
  float mouseloc[2];

  mval[0] = mouse_x;
  mval[1] = 0;

  /* Choose the side based on which side of the current frame the mouse is on. */
  UI_view2d_region_to_view(v2d, mval[0], mval[1], &mouseloc[0], &mouseloc[1]);

  return mouseloc[0] > frame ? SEQ_SIDE_RIGHT : SEQ_SIDE_LEFT;
}

static const EnumPropertyItem prop_split_types[] = {
    {SEQ_SPLIT_SOFT, "SOFT", 0, "Soft", ""},
    {SEQ_SPLIT_HARD, "HARD", 0, "Hard", ""},
    {0, nullptr, 0, nullptr, nullptr},
};

EnumPropertyItem prop_side_types[] = {
    {SEQ_SIDE_MOUSE, "MOUSE", 0, "Mouse Position", ""},
    {SEQ_SIDE_LEFT, "LEFT", 0, "Left", ""},
    {SEQ_SIDE_RIGHT, "RIGHT", 0, "Right", ""},
    {SEQ_SIDE_BOTH, "BOTH", 0, "Both", ""},
    {SEQ_SIDE_NO_CHANGE, "NO_CHANGE", 0, "No Change", ""},
    {0, nullptr, 0, nullptr, nullptr},
};

/* Get the splitting side for the Split Strips's operator exec() callback. */
static int sequence_split_side_for_exec_get(wmOperator *op)
{
  const int split_side = RNA_enum_get(op->ptr, "side");

  /* The mouse position can not be resolved from the exec() as the mouse coordinate is not
   * accessible. So fall-back to the RIGHT side instead.
   *
   * The SEQ_SIDE_MOUSE is used by the Strip menu, together with the EXEC_DEFAULT operator
   * context in order to have properly resolved shortcut in the menu. */
  if (split_side == SEQ_SIDE_MOUSE) {
    return SEQ_SIDE_RIGHT;
  }

  return split_side;
}

static int sequencer_split_exec(bContext *C, wmOperator *op)
{
  Main *bmain = CTX_data_main(C);
  Scene *scene = CTX_data_scene(C);
  Editing *ed = SEQ_editing_get(scene);
  bool changed = false;
  bool seq_selected = false;

  const bool use_cursor_position = RNA_boolean_get(op->ptr, "use_cursor_position");

  const int split_frame = RNA_struct_property_is_set(op->ptr, "frame") ?
                              RNA_int_get(op->ptr, "frame") :
                              scene->r.cfra;
  const int split_channel = RNA_int_get(op->ptr, "channel");

  const eSeqSplitMethod method = eSeqSplitMethod(RNA_enum_get(op->ptr, "type"));
  const int split_side = sequence_split_side_for_exec_get(op);
  const bool ignore_selection = RNA_boolean_get(op->ptr, "ignore_selection");

  SEQ_prefetch_stop(scene);

  LISTBASE_FOREACH (Sequence *, seq, ed->seqbasep) {
    seq->tmp = nullptr;
  }

  LISTBASE_FOREACH_BACKWARD (Sequence *, seq, ed->seqbasep) {
    if (use_cursor_position && seq->machine != split_channel) {
      continue;
    }

    if (ignore_selection || seq->flag & SELECT) {
      const char *error_msg = nullptr;
      if (SEQ_edit_strip_split(bmain, scene, ed->seqbasep, seq, split_frame, method, &error_msg) !=
          nullptr)
      {
        changed = true;
      }
      if (error_msg != nullptr) {
        BKE_report(op->reports, RPT_ERROR, error_msg);
      }
    }
  }

  if (changed) { /* Got new strips? */
    if (ignore_selection) {
      if (use_cursor_position) {
        LISTBASE_FOREACH (Sequence *, seq, SEQ_active_seqbase_get(ed)) {
          if (SEQ_time_right_handle_frame_get(scene, seq) == split_frame &&
              seq->machine == split_channel)
          {
            seq_selected = seq->flag & SEQ_ALLSEL;
          }
        }
        if (!seq_selected) {
          LISTBASE_FOREACH (Sequence *, seq, SEQ_active_seqbase_get(ed)) {
            if (SEQ_time_left_handle_frame_get(scene, seq) == split_frame &&
                seq->machine == split_channel)
            {
              seq->flag &= ~SEQ_ALLSEL;
            }
          }
        }
      }
    }
    else {
      if (split_side != SEQ_SIDE_BOTH) {
        LISTBASE_FOREACH (Sequence *, seq, SEQ_active_seqbase_get(ed)) {
          if (split_side == SEQ_SIDE_LEFT) {
            if (SEQ_time_left_handle_frame_get(scene, seq) >= split_frame) {
              seq->flag &= ~SEQ_ALLSEL;
            }
          }
          else {
            if (SEQ_time_right_handle_frame_get(scene, seq) <= split_frame) {
              seq->flag &= ~SEQ_ALLSEL;
            }
          }
        }
      }
    }
  }
  if (changed) {
    WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
    return OPERATOR_FINISHED;
  }

  /* Passthrough to selection if used as tool. */
  return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH;
}

static int sequencer_split_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
  Scene *scene = CTX_data_scene(C);
  View2D *v2d = UI_view2d_fromcontext(C);

  int split_side = RNA_enum_get(op->ptr, "side");
  int split_frame = scene->r.cfra;

  if (split_side == SEQ_SIDE_MOUSE) {
    if (ED_operator_sequencer_active(C) && v2d) {
      split_side = mouse_frame_side(v2d, event->mval[0], split_frame);
    }
    else {
      split_side = SEQ_SIDE_BOTH;
    }
  }
  float mouseloc[2];
  if (v2d) {
    UI_view2d_region_to_view(v2d, event->mval[0], event->mval[1], &mouseloc[0], &mouseloc[1]);
    if (RNA_boolean_get(op->ptr, "use_cursor_position")) {
      split_frame = mouseloc[0];
    }
    RNA_int_set(op->ptr, "channel", mouseloc[1]);
  }
  RNA_int_set(op->ptr, "frame", split_frame);
  RNA_enum_set(op->ptr, "side", split_side);
  // RNA_enum_set(op->ptr, "type", split_hard);

  return sequencer_split_exec(C, op);
}

static void sequencer_split_ui(bContext * /*C*/, wmOperator *op)
{
  uiLayout *layout = op->layout;
  uiLayoutSetPropSep(layout, true);
  uiLayoutSetPropDecorate(layout, false);

  uiLayout *row = uiLayoutRow(layout, false);
  uiItemR(row, op->ptr, "type", UI_ITEM_R_EXPAND, nullptr, ICON_NONE);
  uiItemR(layout, op->ptr, "frame", UI_ITEM_NONE, nullptr, ICON_NONE);
  uiItemR(layout, op->ptr, "side", UI_ITEM_NONE, nullptr, ICON_NONE);

  uiItemS(layout);

  uiItemR(layout, op->ptr, "use_cursor_position", UI_ITEM_NONE, nullptr, ICON_NONE);
  if (RNA_boolean_get(op->ptr, "use_cursor_position")) {
    uiItemR(layout, op->ptr, "channel", UI_ITEM_NONE, nullptr, ICON_NONE);
  }
}

void SEQUENCER_OT_split(wmOperatorType *ot)
{
  /* Identifiers. */
  ot->name = "Split Strips";
  ot->idname = "SEQUENCER_OT_split";
  ot->description = "Split the selected strips in two";

  /* Api callbacks. */
  ot->invoke = sequencer_split_invoke;
  ot->exec = sequencer_split_exec;
  ot->poll = sequencer_edit_poll;
  ot->ui = sequencer_split_ui;

  /* Flags. */
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

  PropertyRNA *prop;
  RNA_def_int(ot->srna,
              "frame",
              0,
              INT_MIN,
              INT_MAX,
              "Frame",
              "Frame where selected strips will be split",
              INT_MIN,
              INT_MAX);
  RNA_def_int(ot->srna,
              "channel",
              0,
              INT_MIN,
              INT_MAX,
              "Channel",
              "Channel in which strip will be cut",
              INT_MIN,
              INT_MAX);
  RNA_def_enum(ot->srna,
               "type",
               prop_split_types,
               SEQ_SPLIT_SOFT,
               "Type",
               "The type of split operation to perform on strips");

  RNA_def_boolean(ot->srna,
                  "use_cursor_position",
                  false,
                  "Use Cursor Position",
                  "Split at position of the cursor instead of current frame");

  prop = RNA_def_enum(ot->srna,
                      "side",
                      prop_side_types,
                      SEQ_SIDE_MOUSE,
                      "Side",
                      "The side that remains selected after splitting");

  RNA_def_property_flag(prop, PROP_SKIP_SAVE);

  prop = RNA_def_boolean(
      ot->srna,
      "ignore_selection",
      false,
      "Ignore Selection",
      "Make cut even if strip is not selected preserving selection state after cut");

  RNA_def_property_flag(prop, PROP_HIDDEN);
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Duplicate Strips Operator
 * \{ */

static int sequencer_add_duplicate_exec(bContext *C, wmOperator * /*op*/)
{
  Scene *scene = CTX_data_scene(C);
  Editing *ed = SEQ_editing_get(scene);

  if (ed == nullptr) {
    return OPERATOR_CANCELLED;
  }

  Sequence *active_seq = SEQ_select_active_get(scene);
  ListBase duplicated_strips = {nullptr, nullptr};

  SEQ_sequence_base_dupli_recursive(scene, scene, &duplicated_strips, ed->seqbasep, 0, 0);
  ED_sequencer_deselect_all(scene);

  if (duplicated_strips.first == nullptr) {
    return OPERATOR_CANCELLED;
  }

  /* Duplicate animation.
   * First backup original curves from scene and duplicate strip curves from backup into scene.
   * This way, when pasted strips are renamed, curves are renamed with them. Finally, restore
   * original curves from backup.
   */
  SeqAnimationBackup animation_backup = {{nullptr}};
  SEQ_animation_backup_original(scene, &animation_backup);

  Sequence *seq = static_cast<Sequence *>(duplicated_strips.first);

  /* Rely on the `duplicated_strips` list being added at the end.
   * Their UUIDs has been re-generated by the #SEQ_sequence_base_dupli_recursive(). */
  BLI_movelisttolist(ed->seqbasep, &duplicated_strips);

  /* Handle duplicated strips: set active, select, ensure unique name and duplicate animation
   * data. */
  for (; seq; seq = seq->next) {
    if (active_seq != nullptr && STREQ(seq->name, active_seq->name)) {
      SEQ_select_active_set(scene, seq);
    }
    seq->flag &= ~(SEQ_LEFTSEL + SEQ_RIGHTSEL + SEQ_LOCK);
    seq->flag |= SEQ_IGNORE_CHANNEL_LOCK;

    SEQ_animation_duplicate_backup_to_scene(scene, seq, &animation_backup);
    SEQ_ensure_unique_name(seq, scene);
  }

  SEQ_animation_restore_original(scene, &animation_backup);

  DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
  DEG_relations_tag_update(CTX_data_main(C));
  WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
  return OPERATOR_FINISHED;
}

void SEQUENCER_OT_duplicate(wmOperatorType *ot)
{
  /* Identifiers. */
  ot->name = "Duplicate Strips";
  ot->idname = "SEQUENCER_OT_duplicate";
  ot->description = "Duplicate the selected strips";

  /* Api callbacks. */
  ot->exec = sequencer_add_duplicate_exec;
  ot->poll = ED_operator_sequencer_active;

  /* Flags. */
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Erase Strips Operator
 * \{ */

static void sequencer_delete_strip_data(bContext *C, Sequence *seq)
{
  if (seq->type != SEQ_TYPE_SCENE) {
    return;
  }

  Main *bmain = CTX_data_main(C);
  if (seq->scene) {
    if (ED_scene_delete(C, bmain, seq->scene)) {
      WM_event_add_notifier(C, NC_SCENE | NA_REMOVED, seq->scene);
    }
  }
}

static int sequencer_delete_exec(bContext *C, wmOperator *op)
{
  Main *bmain = CTX_data_main(C);
  Scene *scene = CTX_data_scene(C);
  ListBase *seqbasep = SEQ_active_seqbase_get(SEQ_editing_get(scene));
  const bool delete_data = RNA_boolean_get(op->ptr, "delete_data");

  if (sequencer_view_has_preview_poll(C) && !sequencer_view_preview_only_poll(C)) {
    return OPERATOR_CANCELLED;
  }

  if (RNA_boolean_get(op->ptr, "use_retiming_mode")) {
    sequencer_retiming_key_remove_exec(C, op);
  }

  SEQ_prefetch_stop(scene);

  for (Sequence *seq : selected_strips_from_context(C)) {
    SEQ_edit_flag_for_removal(scene, seqbasep, seq);
    if (delete_data) {
      sequencer_delete_strip_data(C, seq);
    }
  }
  SEQ_edit_remove_flagged_sequences(scene, seqbasep);

  DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
  DEG_relations_tag_update(bmain);
  WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
  return OPERATOR_FINISHED;
}

static int sequencer_delete_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
  Scene *scene = CTX_data_scene(C);
  ListBase *markers = &scene->markers;

  if (!BLI_listbase_is_empty(markers)) {
    ARegion *region = CTX_wm_region(C);
    if (region && (region->regiontype == RGN_TYPE_WINDOW)) {
      /* Bounding box of 30 pixels is used for markers shortcuts,
       * prevent conflict with markers shortcuts here. */
      if (event->mval[1] <= 30) {
        return OPERATOR_PASS_THROUGH;
      }
    }
  }

  if (sequencer_retiming_mode_is_active(C)) {
    RNA_boolean_set(op->ptr, "use_retiming_mode", true);
  }

  return sequencer_delete_exec(C, op);
}

static bool sequencer_delete_poll_property(const bContext * /* C */,
                                           wmOperator *op,
                                           const PropertyRNA *prop)
{
  const char *prop_id = RNA_property_identifier(prop);

  if (STREQ(prop_id, "delete_data") && RNA_boolean_get(op->ptr, "use_retiming_mode")) {
    return false;
  }

  return true;
}

void SEQUENCER_OT_delete(wmOperatorType *ot)
{

  /* Identifiers. */
  ot->name = "Delete Strips";
  ot->idname = "SEQUENCER_OT_delete";
  ot->description = "Delete selected strips from the sequencer";

  /* Api callbacks. */
  ot->invoke = sequencer_delete_invoke;
  ot->exec = sequencer_delete_exec;
  ot->poll = sequencer_edit_poll;
  ot->poll_property = sequencer_delete_poll_property;

  /* Flags. */
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

  /*  Properties. */
  ot->prop = RNA_def_boolean(ot->srna,
                             "delete_data",
                             false,
                             "Delete Data",
                             "After removing the Strip, delete the associated data also");
  RNA_def_property_flag(ot->prop, PROP_SKIP_SAVE);

  ot->prop = RNA_def_boolean(ot->srna,
                             "use_retiming_mode",
                             false,
                             "Use Retiming Data",
                             "Operate on retiming data instead of strips");
  RNA_def_property_flag(ot->prop, PROP_HIDDEN);
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Clear Strip Offset Operator
 * \{ */

static int sequencer_offset_clear_exec(bContext *C, wmOperator * /*op*/)
{
  Scene *scene = CTX_data_scene(C);
  Editing *ed = SEQ_editing_get(scene);
  Sequence *seq;
  ListBase *channels = SEQ_channels_displayed_get(SEQ_editing_get(scene));

  /* For effects, try to find a replacement input. */
  for (seq = static_cast<Sequence *>(ed->seqbasep->first); seq;
       seq = static_cast<Sequence *>(seq->next))
  {
    if (SEQ_transform_is_locked(channels, seq)) {
      continue;
    }

    if ((seq->type & SEQ_TYPE_EFFECT) == 0 && (seq->flag & SELECT)) {
      seq->startofs = seq->endofs = 0;
    }
  }

  /* Update lengths, etc. */
  seq = static_cast<Sequence *>(ed->seqbasep->first);
  while (seq) {
    SEQ_relations_invalidate_cache_preprocessed(scene, seq);
    seq = seq->next;
  }

  for (seq = static_cast<Sequence *>(ed->seqbasep->first); seq;
       seq = static_cast<Sequence *>(seq->next))
  {
    if ((seq->type & SEQ_TYPE_EFFECT) == 0 && (seq->flag & SELECT)) {
      if (SEQ_transform_test_overlap(scene, ed->seqbasep, seq)) {
        SEQ_transform_seqbase_shuffle(ed->seqbasep, seq, scene);
      }
    }
  }

  WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);

  return OPERATOR_FINISHED;
}

void SEQUENCER_OT_offset_clear(wmOperatorType *ot)
{

  /* Identifiers. */
  ot->name = "Clear Strip Offset";
  ot->idname = "SEQUENCER_OT_offset_clear";
  ot->description = "Clear strip offsets from the start and end frames";

  /* Api callbacks. */
  ot->exec = sequencer_offset_clear_exec;
  ot->poll = sequencer_edit_poll;

  /* Flags. */
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Separate Images Operator
 * \{ */

static int sequencer_separate_images_exec(bContext *C, wmOperator *op)
{
  Scene *scene = CTX_data_scene(C);
  Editing *ed = SEQ_editing_get(scene);
  ListBase *seqbase = SEQ_active_seqbase_get(ed);

  Sequence *seq, *seq_new;
  Strip *strip_new;
  StripElem *se, *se_new;
  int start_ofs, timeline_frame, frame_end;
  int step = RNA_int_get(op->ptr, "length");

  seq = static_cast<Sequence *>(seqbase->first); /* Poll checks this is valid. */

  SEQ_prefetch_stop(scene);

  while (seq) {
    if ((seq->flag & SELECT) && (seq->type == SEQ_TYPE_IMAGE) && (seq->len > 1)) {
      Sequence *seq_next;

      /* TODO: remove f-curve and assign to split image strips.
       * The old animation system would remove the user of `seq->ipo`. */

      start_ofs = timeline_frame = SEQ_time_left_handle_frame_get(scene, seq);
      frame_end = SEQ_time_right_handle_frame_get(scene, seq);

      while (timeline_frame < frame_end) {
        /* New seq. */
        se = SEQ_render_give_stripelem(scene, seq, timeline_frame);

        seq_new = SEQ_sequence_dupli_recursive(scene, scene, seqbase, seq, SEQ_DUPE_UNIQUE_NAME);

        seq_new->start = start_ofs;
        seq_new->type = SEQ_TYPE_IMAGE;
        seq_new->len = 1;
        seq_new->flag |= SEQ_SINGLE_FRAME_CONTENT;
        seq_new->endofs = 1 - step;

        /* New strip. */
        strip_new = seq_new->strip;
        strip_new->us = 1;

        /* New stripdata, only one element now. */
        /* Note this assume all elements (images) have the same dimension,
         * since we only copy the name here. */
        se_new = static_cast<StripElem *>(MEM_reallocN(strip_new->stripdata, sizeof(*se_new)));
        STRNCPY(se_new->filename, se->filename);
        strip_new->stripdata = se_new;

        if (step > 1) {
          seq_new->flag &= ~SEQ_OVERLAP;
          if (SEQ_transform_test_overlap(scene, seqbase, seq_new)) {
            SEQ_transform_seqbase_shuffle(seqbase, seq_new, scene);
          }
        }

        /* XXX, COPY FCURVES */

        timeline_frame++;
        start_ofs += step;
      }

      seq_next = static_cast<Sequence *>(seq->next);
      SEQ_edit_flag_for_removal(scene, seqbase, seq);
      seq = seq_next;
    }
    else {
      seq = seq->next;
    }
  }

  SEQ_edit_remove_flagged_sequences(scene, seqbase);
  WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);

  return OPERATOR_FINISHED;
}

void SEQUENCER_OT_images_separate(wmOperatorType *ot)
{
  /* Identifiers. */
  ot->name = "Separate Images";
  ot->idname = "SEQUENCER_OT_images_separate";
  ot->description = "On image sequence strips, it returns a strip for each image";

  /* Api callbacks. */
  ot->exec = sequencer_separate_images_exec;
  ot->invoke = WM_operator_props_popup_confirm;
  ot->poll = sequencer_edit_poll;

  /* Flags. */
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

  RNA_def_int(ot->srna, "length", 1, 1, INT_MAX, "Length", "Length of each frame", 1, 1000);
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Toggle Meta Strip Operator
 * \{ */

static int sequencer_meta_toggle_exec(bContext *C, wmOperator * /*op*/)
{
  Scene *scene = CTX_data_scene(C);
  Editing *ed = SEQ_editing_get(scene);
  Sequence *active_seq = SEQ_select_active_get(scene);

  SEQ_prefetch_stop(scene);

  if (active_seq && active_seq->type == SEQ_TYPE_META && active_seq->flag & SELECT) {
    /* Deselect active meta seq. */
    SEQ_select_active_set(scene, nullptr);
    SEQ_meta_stack_set(scene, active_seq);
  }
  else {
    /* Exit meta-strip if possible. */
    if (BLI_listbase_is_empty(&ed->metastack)) {
      return OPERATOR_CANCELLED;
    }

    /* Display parent meta. */
    Sequence *meta_parent = SEQ_meta_stack_pop(ed);
    SEQ_select_active_set(scene, meta_parent);
  }

  DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
  WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);

  return OPERATOR_FINISHED;
}

void SEQUENCER_OT_meta_toggle(wmOperatorType *ot)
{
  /* Identifiers. */
  ot->name = "Toggle Meta Strip";
  ot->idname = "SEQUENCER_OT_meta_toggle";
  ot->description = "Toggle a meta-strip (to edit enclosed strips)";

  /* Api callbacks. */
  ot->exec = sequencer_meta_toggle_exec;
  ot->poll = sequencer_edit_poll;

  /* Flags. */
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Make Meta Strip Operator
 * \{ */

static int sequencer_meta_make_exec(bContext *C, wmOperator *op)
{
  Scene *scene = CTX_data_scene(C);
  Editing *ed = SEQ_editing_get(scene);
  Sequence *active_seq = SEQ_select_active_get(scene);
  ListBase *active_seqbase = SEQ_active_seqbase_get(ed);

  if (SEQ_transform_seqbase_isolated_sel_check(active_seqbase) == false) {
    BKE_report(op->reports, RPT_ERROR, "Please select all related strips");
    return OPERATOR_CANCELLED;
  }

  SEQ_prefetch_stop(scene);

  int channel_max = 1, meta_start_frame = MAXFRAME, meta_end_frame = MINFRAME;
  Sequence *seqm = SEQ_sequence_alloc(active_seqbase, 1, 1, SEQ_TYPE_META);

  /* Remove all selected from main list, and put in meta.
   * Sequence is moved within the same edit, no need to re-generate the UUID. */
  LISTBASE_FOREACH_MUTABLE (Sequence *, seq, active_seqbase) {
    if (seq != seqm && seq->flag & SELECT) {
      BLI_remlink(active_seqbase, seq);
      BLI_addtail(&seqm->seqbase, seq);
      SEQ_relations_invalidate_cache_preprocessed(scene, seq);
      channel_max = max_ii(seq->machine, channel_max);
      meta_start_frame = min_ii(SEQ_time_left_handle_frame_get(scene, seq), meta_start_frame);
      meta_end_frame = max_ii(SEQ_time_right_handle_frame_get(scene, seq), meta_end_frame);
    }
  }

  seqm->machine = active_seq ? active_seq->machine : channel_max;
  BLI_strncpy(seqm->name + 2, "MetaStrip", sizeof(seqm->name) - 2);
  SEQ_sequence_base_unique_name_recursive(scene, &ed->seqbase, seqm);
  seqm->start = meta_start_frame;
  seqm->len = meta_end_frame - meta_start_frame;
  SEQ_select_active_set(scene, seqm);
  if (SEQ_transform_test_overlap(scene, active_seqbase, seqm)) {
    SEQ_transform_seqbase_shuffle(active_seqbase, seqm, scene);
  }

  DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
  WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);

  return OPERATOR_FINISHED;
}

void SEQUENCER_OT_meta_make(wmOperatorType *ot)
{
  /* Identifiers. */
  ot->name = "Make Meta Strip";
  ot->idname = "SEQUENCER_OT_meta_make";
  ot->description = "Group selected strips into a meta-strip";

  /* Api callbacks. */
  ot->exec = sequencer_meta_make_exec;
  ot->poll = sequencer_edit_poll;

  /* Flags. */
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name UnMeta Strip Operator
 * \{ */

static int sequencer_meta_separate_exec(bContext *C, wmOperator * /*op*/)
{
  Scene *scene = CTX_data_scene(C);
  Editing *ed = SEQ_editing_get(scene);
  Sequence *active_seq = SEQ_select_active_get(scene);

  if (active_seq == nullptr || active_seq->type != SEQ_TYPE_META) {
    return OPERATOR_CANCELLED;
  }

  SEQ_prefetch_stop(scene);

  LISTBASE_FOREACH (Sequence *, seq, &active_seq->seqbase) {
    SEQ_relations_invalidate_cache_preprocessed(scene, seq);
  }

  /* Remove all selected from meta, and put in main list.
   * Sequence is moved within the same edit, no need to re-generate the UUID. */
  BLI_movelisttolist(ed->seqbasep, &active_seq->seqbase);
  BLI_listbase_clear(&active_seq->seqbase);

  ListBase *active_seqbase = SEQ_active_seqbase_get(ed);
  SEQ_edit_flag_for_removal(scene, active_seqbase, active_seq);
  SEQ_edit_remove_flagged_sequences(scene, active_seqbase);

  /* Test for effects and overlap. */
  LISTBASE_FOREACH (Sequence *, seq, active_seqbase) {
    if (seq->flag & SELECT) {
      seq->flag &= ~SEQ_OVERLAP;
      if (SEQ_transform_test_overlap(scene, active_seqbase, seq)) {
        SEQ_transform_seqbase_shuffle(active_seqbase, seq, scene);
      }
    }
  }

  DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
  WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);

  return OPERATOR_FINISHED;
}

void SEQUENCER_OT_meta_separate(wmOperatorType *ot)
{
  /* Identifiers. */
  ot->name = "UnMeta Strip";
  ot->idname = "SEQUENCER_OT_meta_separate";
  ot->description = "Put the contents of a meta-strip back in the sequencer";

  /* Api callbacks. */
  ot->exec = sequencer_meta_separate_exec;
  ot->poll = sequencer_edit_poll;

  /* Flags. */
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Jump to Strip Operator
 * \{ */

static bool strip_jump_internal(Scene *scene,
                                const short side,
                                const bool do_skip_mute,
                                const bool do_center)
{
  bool changed = false;
  int timeline_frame = scene->r.cfra;
  int next_frame = SEQ_time_find_next_prev_edit(
      scene, timeline_frame, side, do_skip_mute, do_center, false);

  if (next_frame != timeline_frame) {
    scene->r.cfra = next_frame;
    changed = true;
  }

  return changed;
}

static bool sequencer_strip_jump_poll(bContext *C)
{
  /* Prevent changes during render. */
  if (G.is_rendering) {
    return false;
  }

  return sequencer_edit_poll(C);
}

static int sequencer_strip_jump_exec(bContext *C, wmOperator *op)
{
  Scene *scene = CTX_data_scene(C);
  const bool next = RNA_boolean_get(op->ptr, "next");
  const bool center = RNA_boolean_get(op->ptr, "center");

  /* Currently do_skip_mute is always true. */
  if (!strip_jump_internal(scene, next ? SEQ_SIDE_RIGHT : SEQ_SIDE_LEFT, true, center)) {
    return OPERATOR_CANCELLED;
  }

  DEG_id_tag_update(&scene->id, ID_RECALC_FRAME_CHANGE);
  WM_event_add_notifier(C, NC_SCENE | ND_FRAME, scene);

  return OPERATOR_FINISHED;
}

void SEQUENCER_OT_strip_jump(wmOperatorType *ot)
{
  /* Identifiers. */
  ot->name = "Jump to Strip";
  ot->idname = "SEQUENCER_OT_strip_jump";
  ot->description = "Move frame to previous edit point";

  /* Api callbacks. */
  ot->exec = sequencer_strip_jump_exec;
  ot->poll = sequencer_strip_jump_poll;

  /* Flags. */
  ot->flag = OPTYPE_UNDO;

  /* Properties. */
  RNA_def_boolean(ot->srna, "next", true, "Next Strip", "");
  RNA_def_boolean(ot->srna, "center", true, "Use Strip Center", "");
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Swap Strip Operator
 * \{ */

static const EnumPropertyItem prop_side_lr_types[] = {
    {SEQ_SIDE_LEFT, "LEFT", 0, "Left", ""},
    {SEQ_SIDE_RIGHT, "RIGHT", 0, "Right", ""},
    {0, nullptr, 0, nullptr, nullptr},
};

static void swap_sequence(Scene *scene, Sequence *seqa, Sequence *seqb)
{
  int gap = SEQ_time_left_handle_frame_get(scene, seqb) -
            SEQ_time_right_handle_frame_get(scene, seqa);
  int seq_a_start;
  int seq_b_start;

  seq_b_start = (seqb->start - SEQ_time_left_handle_frame_get(scene, seqb)) +
                SEQ_time_left_handle_frame_get(scene, seqa);
  SEQ_transform_translate_sequence(scene, seqb, seq_b_start - seqb->start);
  SEQ_relations_invalidate_cache_preprocessed(scene, seqb);

  seq_a_start = (seqa->start - SEQ_time_left_handle_frame_get(scene, seqa)) +
                SEQ_time_right_handle_frame_get(scene, seqb) + gap;
  SEQ_transform_translate_sequence(scene, seqa, seq_a_start - seqa->start);
  SEQ_relations_invalidate_cache_preprocessed(scene, seqa);
}

static Sequence *find_next_prev_sequence(Scene *scene, Sequence *test, int lr, int sel)
{
  /* sel: 0==unselected, 1==selected, -1==don't care. */
  Sequence *seq, *best_seq = nullptr;
  Editing *ed = SEQ_editing_get(scene);

  int dist, best_dist;
  best_dist = MAXFRAME * 2;

  if (ed == nullptr) {
    return nullptr;
  }

  seq = static_cast<Sequence *>(ed->seqbasep->first);
  while (seq) {
    if ((seq != test) && (test->machine == seq->machine) &&
        ((sel == -1) || (sel == (seq->flag & SELECT))))
    {
      dist = MAXFRAME * 2;

      switch (lr) {
        case SEQ_SIDE_LEFT:
          if (SEQ_time_right_handle_frame_get(scene, seq) <=
              SEQ_time_left_handle_frame_get(scene, test))
          {
            dist = SEQ_time_right_handle_frame_get(scene, test) -
                   SEQ_time_left_handle_frame_get(scene, seq);
          }
          break;
        case SEQ_SIDE_RIGHT:
          if (SEQ_time_left_handle_frame_get(scene, seq) >=
              SEQ_time_right_handle_frame_get(scene, test))
          {
            dist = SEQ_time_left_handle_frame_get(scene, seq) -
                   SEQ_time_right_handle_frame_get(scene, test);
          }
          break;
      }

      if (dist == 0) {
        best_seq = seq;
        break;
      }
      if (dist < best_dist) {
        best_dist = dist;
        best_seq = seq;
      }
    }
    seq = static_cast<Sequence *>(seq->next);
  }
  return best_seq; /* Can be nullptr. */
}

static bool seq_is_parent(Sequence *par, Sequence *seq)
{
  return ((par->seq1 == seq) || (par->seq2 == seq) || (par->seq3 == seq));
}

static int sequencer_swap_exec(bContext *C, wmOperator *op)
{
  Scene *scene = CTX_data_scene(C);
  Editing *ed = SEQ_editing_get(scene);
  Sequence *active_seq = SEQ_select_active_get(scene);
  ListBase *seqbase = SEQ_active_seqbase_get(ed);
  Sequence *seq;
  int side = RNA_enum_get(op->ptr, "side");

  if (active_seq == nullptr) {
    return OPERATOR_CANCELLED;
  }

  seq = find_next_prev_sequence(scene, active_seq, side, -1);

  if (seq) {

    /* Disallow effect strips. */
    if (SEQ_effect_get_num_inputs(seq->type) >= 1 &&
        (seq->effectdata || seq->seq1 || seq->seq2 || seq->seq3))
    {
      return OPERATOR_CANCELLED;
    }
    if ((SEQ_effect_get_num_inputs(active_seq->type) >= 1) &&
        (active_seq->effectdata || active_seq->seq1 || active_seq->seq2 || active_seq->seq3))
    {
      return OPERATOR_CANCELLED;
    }

    ListBase *channels = SEQ_channels_displayed_get(SEQ_editing_get(scene));
    if (SEQ_transform_is_locked(channels, seq) || SEQ_transform_is_locked(channels, active_seq)) {
      return OPERATOR_CANCELLED;
    }

    switch (side) {
      case SEQ_SIDE_LEFT:
        swap_sequence(scene, seq, active_seq);
        break;
      case SEQ_SIDE_RIGHT:
        swap_sequence(scene, active_seq, seq);
        break;
    }

    /* Do this in a new loop since both effects need to be calculated first. */
    LISTBASE_FOREACH (Sequence *, iseq, seqbase) {
      if ((iseq->type & SEQ_TYPE_EFFECT) &&
          (seq_is_parent(iseq, active_seq) || seq_is_parent(iseq, seq)))
      {
        /* This may now overlap. */
        if (SEQ_transform_test_overlap(scene, seqbase, iseq)) {
          SEQ_transform_seqbase_shuffle(seqbase, iseq, scene);
        }
      }
    }

    WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
    return OPERATOR_FINISHED;
  }

  return OPERATOR_CANCELLED;
}

void SEQUENCER_OT_swap(wmOperatorType *ot)
{
  /* Identifiers. */
  ot->name = "Swap Strip";
  ot->idname = "SEQUENCER_OT_swap";
  ot->description = "Swap active strip with strip to the right or left";

  /* Api callbacks. */
  ot->exec = sequencer_swap_exec;
  ot->poll = sequencer_edit_poll;

  /* Flags. */
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

  /* Properties. */
  RNA_def_enum(
      ot->srna, "side", prop_side_lr_types, SEQ_SIDE_RIGHT, "Side", "Side of the strip to swap");
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Set Render Size Operator
 * \{ */

static int sequencer_rendersize_exec(bContext *C, wmOperator * /*op*/)
{
  Scene *scene = CTX_data_scene(C);
  Sequence *active_seq = SEQ_select_active_get(scene);
  StripElem *se = nullptr;

  if (active_seq == nullptr || active_seq->strip == nullptr) {
    return OPERATOR_CANCELLED;
  }

  switch (active_seq->type) {
    case SEQ_TYPE_IMAGE:
      se = SEQ_render_give_stripelem(scene, active_seq, scene->r.cfra);
      break;
    case SEQ_TYPE_MOVIE:
      se = active_seq->strip->stripdata;
      break;
    default:
      return OPERATOR_CANCELLED;
  }

  if (se == nullptr) {
    return OPERATOR_CANCELLED;
  }

  /* Prevent setting the render size if sequence values aren't initialized. */
  if (se->orig_width <= 0 || se->orig_height <= 0) {
    return OPERATOR_CANCELLED;
  }

  scene->r.xsch = se->orig_width;
  scene->r.ysch = se->orig_height;

  active_seq->strip->transform->scale_x = active_seq->strip->transform->scale_y = 1.0f;
  active_seq->strip->transform->xofs = active_seq->strip->transform->yofs = 0.0f;

  SEQ_relations_invalidate_cache_preprocessed(scene, active_seq);
  WM_event_add_notifier(C, NC_SCENE | ND_RENDER_OPTIONS, scene);
  WM_event_add_notifier(C, NC_SPACE | ND_SPACE_SEQUENCER, nullptr);

  return OPERATOR_FINISHED;
}

void SEQUENCER_OT_rendersize(wmOperatorType *ot)
{
  /* Identifiers. */
  ot->name = "Set Render Size";
  ot->idname = "SEQUENCER_OT_rendersize";
  ot->description = "Set render size and aspect from active sequence";

  /* Api callbacks. */
  ot->exec = sequencer_rendersize_exec;
  ot->poll = sequencer_edit_poll;

  /* Flags. */
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Copy Operator
 * \{ */

void SEQUENCER_OT_copy(wmOperatorType *ot)
{
  /* Identifiers. */
  ot->name = "Copy";
  ot->idname = "SEQUENCER_OT_copy";
  ot->description = "Copy the selected strips to the internal clipboard";

  /* Api callbacks. */
  ot->exec = sequencer_clipboard_copy_exec;
  ot->poll = sequencer_edit_poll;

  /* Flags. */
  ot->flag = OPTYPE_REGISTER;
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Paste Operator
 * \{ */

bool ED_sequencer_deselect_all(Scene *scene)
{
  Editing *ed = SEQ_editing_get(scene);
  bool changed = false;

  if (ed == nullptr) {
    return changed;
  }

  LISTBASE_FOREACH (Sequence *, seq, SEQ_active_seqbase_get(ed)) {
    if (seq->flag & SEQ_ALLSEL) {
      seq->flag &= ~SEQ_ALLSEL;
      changed = true;
    }
  }
  return changed;
}

void SEQUENCER_OT_paste(wmOperatorType *ot)
{
  /* Identifiers. */
  ot->name = "Paste";
  ot->idname = "SEQUENCER_OT_paste";
  ot->description = "Paste strips from the internal clipboard";

  /* Api callbacks. */
  ot->exec = sequencer_clipboard_paste_exec;
  ot->poll = ED_operator_sequencer_active;

  /* Flags. */
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

  /* Properties. */
  PropertyRNA *prop = RNA_def_boolean(
      ot->srna,
      "keep_offset",
      false,
      "Keep Offset",
      "Keep strip offset relative to the current frame when pasting");
  RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Sequencer Swap Data Operator
 * \{ */

static int sequencer_swap_data_exec(bContext *C, wmOperator *op)
{
  Scene *scene = CTX_data_scene(C);
  Sequence *seq_act;
  Sequence *seq_other;
  const char *error_msg;

  if (SEQ_select_active_get_pair(scene, &seq_act, &seq_other) == false) {
    BKE_report(op->reports, RPT_ERROR, "Please select two strips");
    return OPERATOR_CANCELLED;
  }

  if (SEQ_edit_sequence_swap(scene, seq_act, seq_other, &error_msg) == false) {
    BKE_report(op->reports, RPT_ERROR, error_msg);
    return OPERATOR_CANCELLED;
  }

  if (seq_act->scene_sound) {
    BKE_sound_remove_scene_sound(scene, seq_act->scene_sound);
  }

  if (seq_other->scene_sound) {
    BKE_sound_remove_scene_sound(scene, seq_other->scene_sound);
  }

  seq_act->scene_sound = nullptr;
  seq_other->scene_sound = nullptr;

  if (seq_act->sound) {
    BKE_sound_add_scene_sound_defaults(scene, seq_act);
  }
  if (seq_other->sound) {
    BKE_sound_add_scene_sound_defaults(scene, seq_other);
  }

  SEQ_relations_invalidate_cache_raw(scene, seq_act);
  SEQ_relations_invalidate_cache_raw(scene, seq_other);

  WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);

  return OPERATOR_FINISHED;
}

void SEQUENCER_OT_swap_data(wmOperatorType *ot)
{
  /* Identifiers. */
  ot->name = "Sequencer Swap Data";
  ot->idname = "SEQUENCER_OT_swap_data";
  ot->description = "Swap 2 sequencer strips";

  /* Api callbacks. */
  ot->exec = sequencer_swap_data_exec;
  ot->poll = ED_operator_sequencer_active;

  /* Flags. */
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Change Effect Input Operator
 * \{ */

static const EnumPropertyItem prop_change_effect_input_types[] = {
    {0, "A_B", 0, "A " BLI_STR_UTF8_RIGHTWARDS_ARROW " B", ""},
    {1, "B_C", 0, "B " BLI_STR_UTF8_RIGHTWARDS_ARROW " C", ""},
    {2, "A_C", 0, "A " BLI_STR_UTF8_RIGHTWARDS_ARROW " C", ""},
    {0, nullptr, 0, nullptr, nullptr},
};

static int sequencer_change_effect_input_exec(bContext *C, wmOperator *op)
{
  Scene *scene = CTX_data_scene(C);
  Sequence *seq = SEQ_select_active_get(scene);

  Sequence **seq_1, **seq_2;

  switch (RNA_enum_get(op->ptr, "swap")) {
    case 0:
      seq_1 = &seq->seq1;
      seq_2 = &seq->seq2;
      break;
    case 1:
      seq_1 = &seq->seq2;
      seq_2 = &seq->seq3;
      break;
    default: /* 2 */
      seq_1 = &seq->seq1;
      seq_2 = &seq->seq3;
      break;
  }

  if (*seq_1 == nullptr || *seq_2 == nullptr) {
    BKE_report(op->reports, RPT_ERROR, "One of the effect inputs is unset, cannot swap");
    return OPERATOR_CANCELLED;
  }

  SWAP(Sequence *, *seq_1, *seq_2);

  SEQ_relations_invalidate_cache_preprocessed(scene, seq);
  WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);

  return OPERATOR_FINISHED;
}

void SEQUENCER_OT_change_effect_input(wmOperatorType *ot)
{
  /* Identifiers. */
  ot->name = "Change Effect Input";
  ot->idname = "SEQUENCER_OT_change_effect_input";

  /* Api callbacks. */
  ot->exec = sequencer_change_effect_input_exec;
  ot->poll = sequencer_effect_poll;

  /* Flags. */
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

  ot->prop = RNA_def_enum(
      ot->srna, "swap", prop_change_effect_input_types, 0, "Swap", "The effect inputs to swap");
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Change Effect Type Operator
 * \{ */

EnumPropertyItem sequencer_prop_effect_types[] = {
    {SEQ_TYPE_CROSS, "CROSS", 0, "Crossfade", "Crossfade effect strip type"},
    {SEQ_TYPE_ADD, "ADD", 0, "Add", "Add effect strip type"},
    {SEQ_TYPE_SUB, "SUBTRACT", 0, "Subtract", "Subtract effect strip type"},
    {SEQ_TYPE_ALPHAOVER, "ALPHA_OVER", 0, "Alpha Over", "Alpha Over effect strip type"},
    {SEQ_TYPE_ALPHAUNDER, "ALPHA_UNDER", 0, "Alpha Under", "Alpha Under effect strip type"},
    {SEQ_TYPE_GAMCROSS, "GAMMA_CROSS", 0, "Gamma Cross", "Gamma Cross effect strip type"},
    {SEQ_TYPE_MUL, "MULTIPLY", 0, "Multiply", "Multiply effect strip type"},
    {SEQ_TYPE_OVERDROP, "OVER_DROP", 0, "Alpha Over Drop", "Alpha Over Drop effect strip type"},
    {SEQ_TYPE_WIPE, "WIPE", 0, "Wipe", "Wipe effect strip type"},
    {SEQ_TYPE_GLOW, "GLOW", 0, "Glow", "Glow effect strip type"},
    {SEQ_TYPE_TRANSFORM, "TRANSFORM", 0, "Transform", "Transform effect strip type"},
    {SEQ_TYPE_COLOR, "COLOR", 0, "Color", "Color effect strip type"},
    {SEQ_TYPE_SPEED, "SPEED", 0, "Speed", "Color effect strip type"},
    {SEQ_TYPE_MULTICAM, "MULTICAM", 0, "Multicam Selector", ""},
    {SEQ_TYPE_ADJUSTMENT, "ADJUSTMENT", 0, "Adjustment Layer", ""},
    {SEQ_TYPE_GAUSSIAN_BLUR, "GAUSSIAN_BLUR", 0, "Gaussian Blur", ""},
    {SEQ_TYPE_TEXT, "TEXT", 0, "Text", ""},
    {SEQ_TYPE_COLORMIX, "COLORMIX", 0, "Color Mix", ""},
    {0, nullptr, 0, nullptr, nullptr},
};

static int sequencer_change_effect_type_exec(bContext *C, wmOperator *op)
{
  Scene *scene = CTX_data_scene(C);
  Sequence *seq = SEQ_select_active_get(scene);
  const int new_type = RNA_enum_get(op->ptr, "type");

  /* Free previous effect and init new effect. */
  SeqEffectHandle sh;

  if ((seq->type & SEQ_TYPE_EFFECT) == 0) {
    return OPERATOR_CANCELLED;
  }

  /* Can someone explain the logic behind only allowing to increase this,
   * copied from 2.4x - campbell */
  if (SEQ_effect_get_num_inputs(seq->type) < SEQ_effect_get_num_inputs(new_type)) {
    BKE_report(op->reports, RPT_ERROR, "New effect needs more input strips");
    return OPERATOR_CANCELLED;
  }

  sh = SEQ_effect_handle_get(seq);
  sh.free(seq, true);

  seq->type = new_type;

  sh = SEQ_effect_handle_get(seq);
  sh.init(seq);

  SEQ_relations_invalidate_cache_preprocessed(scene, seq);
  WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);

  return OPERATOR_FINISHED;
}

void SEQUENCER_OT_change_effect_type(wmOperatorType *ot)
{
  /* Identifiers. */
  ot->name = "Change Effect Type";
  ot->idname = "SEQUENCER_OT_change_effect_type";

  /* Api callbacks. */
  ot->exec = sequencer_change_effect_type_exec;
  ot->poll = sequencer_effect_poll;

  /* Flags. */
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

  ot->prop = RNA_def_enum(ot->srna,
                          "type",
                          sequencer_prop_effect_types,
                          SEQ_TYPE_CROSS,
                          "Type",
                          "Sequencer effect type");
  RNA_def_property_translation_context(ot->prop, BLT_I18NCONTEXT_ID_SEQUENCE);
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Change Data/Files Operator
 * \{ */

static int sequencer_change_path_exec(bContext *C, wmOperator *op)
{
  Main *bmain = CTX_data_main(C);
  Scene *scene = CTX_data_scene(C);
  Sequence *seq = SEQ_select_active_get(scene);
  const bool is_relative_path = RNA_boolean_get(op->ptr, "relative_path");
  const bool use_placeholders = RNA_boolean_get(op->ptr, "use_placeholders");
  int minext_frameme, numdigits;

  if (seq->type == SEQ_TYPE_IMAGE) {
    char directory[FILE_MAX];
    int len;
    StripElem *se;

    /* Need to find min/max frame for placeholders. */
    if (use_placeholders) {
      len = sequencer_image_seq_get_minmax_frame(op, seq->sfra, &minext_frameme, &numdigits);
    }
    else {
      len = RNA_property_collection_length(op->ptr, RNA_struct_find_property(op->ptr, "files"));
    }
    if (len == 0) {
      return OPERATOR_CANCELLED;
    }

    RNA_string_get(op->ptr, "directory", directory);
    if (is_relative_path) {
      /* TODO(@ideasman42): shouldn't this already be relative from the filesel?
       * (as the 'filepath' is) for now just make relative here,
       * but look into changing after 2.60. */
      BLI_path_rel(directory, BKE_main_blendfile_path(bmain));
    }
    STRNCPY(seq->strip->dirpath, directory);

    if (seq->strip->stripdata) {
      MEM_freeN(seq->strip->stripdata);
    }
    seq->strip->stripdata = se = MEM_cnew_array<StripElem>(len, "stripelem");

    if (use_placeholders) {
      sequencer_image_seq_reserve_frames(op, se, len, minext_frameme, numdigits);
    }
    else {
      RNA_BEGIN (op->ptr, itemptr, "files") {
        char *filename = RNA_string_get_alloc(&itemptr, "name", nullptr, 0, nullptr);
        STRNCPY(se->filename, filename);
        MEM_freeN(filename);
        se++;
      }
      RNA_END;
    }

    if (len == 1) {
      seq->flag |= SEQ_SINGLE_FRAME_CONTENT;
    }
    else {
      seq->flag &= ~SEQ_SINGLE_FRAME_CONTENT;
    }

    /* Reset these else we won't see all the images. */
    seq->anim_startofs = seq->anim_endofs = 0;

    /* Correct start/end frames so we don't move.
     * Important not to set seq->len = len; allow the function to handle it. */
    SEQ_add_reload_new_file(bmain, scene, seq, true);
  }
  else if (seq->type == SEQ_TYPE_SOUND_RAM) {
    bSound *sound = seq->sound;
    if (sound == nullptr) {
      return OPERATOR_CANCELLED;
    }
    char filepath[FILE_MAX];
    RNA_string_get(op->ptr, "filepath", filepath);
    STRNCPY(sound->filepath, filepath);
    BKE_sound_load(bmain, sound);
  }
  else {
    /* Lame, set rna filepath. */
    PropertyRNA *prop;
    char filepath[FILE_MAX];

    PointerRNA seq_ptr = RNA_pointer_create(&scene->id, &RNA_Sequence, seq);

    RNA_string_get(op->ptr, "filepath", filepath);
    prop = RNA_struct_find_property(&seq_ptr, "filepath");
    RNA_property_string_set(&seq_ptr, prop, filepath);
    RNA_property_update(C, &seq_ptr, prop);
    SEQ_relations_sequence_free_anim(seq);
  }

  SEQ_relations_invalidate_cache_raw(scene, seq);
  WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);

  return OPERATOR_FINISHED;
}

static int sequencer_change_path_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
{
  Scene *scene = CTX_data_scene(C);
  Sequence *seq = SEQ_select_active_get(scene);
  char filepath[FILE_MAX];

  BLI_path_join(filepath, sizeof(filepath), seq->strip->dirpath, seq->strip->stripdata->filename);

  RNA_string_set(op->ptr, "directory", seq->strip->dirpath);
  RNA_string_set(op->ptr, "filepath", filepath);

  /* Set default display depending on seq type. */
  if (seq->type == SEQ_TYPE_IMAGE) {
    RNA_boolean_set(op->ptr, "filter_movie", false);
  }
  else {
    RNA_boolean_set(op->ptr, "filter_image", false);
  }

  WM_event_add_fileselect(C, op);

  return OPERATOR_RUNNING_MODAL;
}

void SEQUENCER_OT_change_path(wmOperatorType *ot)
{
  /* Identifiers. */
  ot->name = "Change Data/Files";
  ot->idname = "SEQUENCER_OT_change_path";

  /* Api callbacks. */
  ot->exec = sequencer_change_path_exec;
  ot->invoke = sequencer_change_path_invoke;
  ot->poll = sequencer_strip_has_path_poll;

  /* Flags. */
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

  WM_operator_properties_filesel(ot,
                                 FILE_TYPE_FOLDER,
                                 FILE_SPECIAL,
                                 FILE_OPENFILE,
                                 WM_FILESEL_DIRECTORY | WM_FILESEL_RELPATH | WM_FILESEL_FILEPATH |
                                     WM_FILESEL_FILES,
                                 FILE_DEFAULTDISPLAY,
                                 FILE_SORT_DEFAULT);
  RNA_def_boolean(ot->srna,
                  "use_placeholders",
                  false,
                  "Use Placeholders",
                  "Use placeholders for missing frames of the strip");
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Change Strip Scene Operator
 * \{ */

static bool sequencer_strip_change_scene_poll(bContext *C)
{
  Editing *ed = SEQ_editing_get(CTX_data_scene(C));
  if (ed == nullptr) {
    return false;
  }
  Sequence *seq = ed->act_seq;
  return ((seq != nullptr) && (seq->type == SEQ_TYPE_SCENE));
}
static int sequencer_change_scene_exec(bContext *C, wmOperator *op)
{
  Main *bmain = CTX_data_main(C);
  Scene *scene = CTX_data_scene(C);
  Scene *scene_seq = static_cast<Scene *>(
      BLI_findlink(&bmain->scenes, RNA_enum_get(op->ptr, "scene")));

  if (scene_seq == nullptr) {
    BKE_report(op->reports, RPT_ERROR, "Scene not found");
    return OPERATOR_CANCELLED;
  }

  /* Assign new scene. */
  Sequence *seq = SEQ_select_active_get(scene);
  if (seq) {
    seq->scene = scene_seq;
    /* Do a refresh of the sequencer data. */
    SEQ_relations_invalidate_cache_raw(scene, seq);
    DEG_id_tag_update(&scene->id, ID_RECALC_AUDIO | ID_RECALC_SEQUENCER_STRIPS);
    DEG_relations_tag_update(bmain);
  }

  WM_event_add_notifier(C, NC_SCENE | ND_SCENEBROWSE, scene);
  WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);

  return OPERATOR_FINISHED;
}

static int sequencer_change_scene_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
  if (!RNA_struct_property_is_set(op->ptr, "scene")) {
    return WM_enum_search_invoke(C, op, event);
  }

  return sequencer_change_scene_exec(C, op);
}

void SEQUENCER_OT_change_scene(wmOperatorType *ot)
{
  PropertyRNA *prop;

  /* Identifiers. */
  ot->name = "Change Scene";
  ot->idname = "SEQUENCER_OT_change_scene";
  ot->description = "Change Scene assigned to Strip";

  /* Api callbacks. */
  ot->exec = sequencer_change_scene_exec;
  ot->invoke = sequencer_change_scene_invoke;
  ot->poll = sequencer_strip_change_scene_poll;

  /* Flags. */
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

  /* Properties. */
  prop = RNA_def_enum(ot->srna, "scene", rna_enum_dummy_NULL_items, 0, "Scene", "");
  RNA_def_enum_funcs(prop, RNA_scene_without_active_itemf);
  RNA_def_property_flag(prop, PROP_ENUM_NO_TRANSLATE);
  ot->prop = prop;
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Export Subtitles Operator
 * \{ */

/** Comparison function suitable to be used with BLI_listbase_sort(). */
static int seq_cmp_time_startdisp_channel(void *thunk, const void *a, const void *b)
{
  const Scene *scene = static_cast<Scene *>(thunk);
  const Sequence *seq_a = static_cast<const Sequence *>(a);
  const Sequence *seq_b = static_cast<const Sequence *>(b);

  int seq_a_start = SEQ_time_left_handle_frame_get(scene, seq_a);
  int seq_b_start = SEQ_time_left_handle_frame_get(scene, seq_b);

  /* If strips have the same start frame favor the one with a higher channel. */
  if (seq_a_start == seq_b_start) {
    return seq_a->machine > seq_b->machine;
  }

  return (seq_a_start > seq_b_start);
}

static int sequencer_export_subtitles_invoke(bContext *C,
                                             wmOperator *op,
                                             const wmEvent * /*event*/)
{
  ED_fileselect_ensure_default_filepath(C, op, ".srt");

  WM_event_add_fileselect(C, op);

  return OPERATOR_RUNNING_MODAL;
}

struct Seq_get_text_cb_data {
  ListBase *text_seq;
  Scene *scene;
};

static bool seq_get_text_strip_cb(Sequence *seq, void *user_data)
{
  Seq_get_text_cb_data *cd = (Seq_get_text_cb_data *)user_data;
  Editing *ed = SEQ_editing_get(cd->scene);
  ListBase *channels = SEQ_channels_displayed_get(ed);
  /* Only text strips that are not muted and don't end with negative frame. */
  if ((seq->type == SEQ_TYPE_TEXT) && !SEQ_render_is_muted(channels, seq) &&
      (SEQ_time_right_handle_frame_get(cd->scene, seq) > cd->scene->r.sfra))
  {
    BLI_addtail(cd->text_seq, MEM_dupallocN(seq));
  }
  return true;
}

static int sequencer_export_subtitles_exec(bContext *C, wmOperator *op)
{
  Scene *scene = CTX_data_scene(C);
  Sequence *seq, *seq_next;
  Editing *ed = SEQ_editing_get(scene);
  ListBase text_seq = {nullptr};
  int iter = 1; /* Sequence numbers in .srt files are 1-indexed. */
  FILE *file;
  char filepath[FILE_MAX];

  if (!RNA_struct_property_is_set_ex(op->ptr, "filepath", false)) {
    BKE_report(op->reports, RPT_ERROR, "No filepath given");
    return OPERATOR_CANCELLED;
  }

  RNA_string_get(op->ptr, "filepath", filepath);
  BLI_path_extension_ensure(filepath, sizeof(filepath), ".srt");

  /* Avoid File write exceptions. */
  if (!BLI_exists(filepath)) {
    BLI_file_ensure_parent_dir_exists(filepath);
    if (!BLI_file_touch(filepath)) {
      BKE_report(op->reports, RPT_ERROR, "Can't create subtitle file");
      return OPERATOR_CANCELLED;
    }
  }
  else if (!BLI_file_is_writable(filepath)) {
    BKE_report(op->reports, RPT_ERROR, "Can't overwrite export file");
    return OPERATOR_CANCELLED;
  }

  if (ed != nullptr) {
    Seq_get_text_cb_data cb_data = {&text_seq, scene};
    SEQ_for_each_callback(&ed->seqbase, seq_get_text_strip_cb, &cb_data);
  }

  if (BLI_listbase_is_empty(&text_seq)) {
    BKE_report(op->reports, RPT_ERROR, "No subtitles (text strips) to export");
    return OPERATOR_CANCELLED;
  }

  BLI_listbase_sort_r(&text_seq, seq_cmp_time_startdisp_channel, scene);

  /* Open and write file. */
  file = BLI_fopen(filepath, "w");

  for (seq = static_cast<Sequence *>(text_seq.first); seq; seq = seq_next) {
    TextVars *data = static_cast<TextVars *>(seq->effectdata);
    char timecode_str_start[32];
    char timecode_str_end[32];

    /* Write time-code relative to start frame of scene. Don't allow negative time-codes. */
    BLI_timecode_string_from_time(
        timecode_str_start,
        sizeof(timecode_str_start),
        -2,
        FRA2TIME(max_ii(SEQ_time_left_handle_frame_get(scene, seq) - scene->r.sfra, 0)),
        FPS,
        USER_TIMECODE_SUBRIP);
    BLI_timecode_string_from_time(
        timecode_str_end,
        sizeof(timecode_str_end),
        -2,
        FRA2TIME(SEQ_time_right_handle_frame_get(scene, seq) - scene->r.sfra),
        FPS,
        USER_TIMECODE_SUBRIP);

    fprintf(
        file, "%d\n%s --> %s\n%s\n\n", iter++, timecode_str_start, timecode_str_end, data->text);

    seq_next = static_cast<Sequence *>(seq->next);
    MEM_freeN(seq);
  }

  fclose(file);

  return OPERATOR_FINISHED;
}

static bool sequencer_strip_is_text_poll(bContext *C)
{
  Editing *ed;
  Sequence *seq;
  return (((ed = SEQ_editing_get(CTX_data_scene(C))) != nullptr) &&
          ((seq = ed->act_seq) != nullptr) && (seq->type == SEQ_TYPE_TEXT));
}

void SEQUENCER_OT_export_subtitles(wmOperatorType *ot)
{
  /* Identifiers. */
  ot->name = "Export Subtitles";
  ot->idname = "SEQUENCER_OT_export_subtitles";
  ot->description = "Export .srt file containing text strips";

  /* Api callbacks. */
  ot->exec = sequencer_export_subtitles_exec;
  ot->invoke = sequencer_export_subtitles_invoke;
  ot->poll = sequencer_strip_is_text_poll;

  /* Flags. */
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

  WM_operator_properties_filesel(ot,
                                 FILE_TYPE_FOLDER,
                                 FILE_BLENDER,
                                 FILE_SAVE,
                                 WM_FILESEL_FILEPATH,
                                 FILE_DEFAULTDISPLAY,
                                 FILE_SORT_DEFAULT);
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Set Range to Strips Operator
 * \{ */

static int sequencer_set_range_to_strips_exec(bContext *C, wmOperator *op)
{
  Scene *scene = CTX_data_scene(C);
  Editing *ed = SEQ_editing_get(scene);

  int sfra = MAXFRAME;
  int efra = -MAXFRAME;
  bool selected = false;
  const bool preview = RNA_boolean_get(op->ptr, "preview");

  LISTBASE_FOREACH (Sequence *, seq, ed->seqbasep) {
    if (seq->flag & SELECT) {
      selected = true;
      sfra = min_ii(sfra, SEQ_time_left_handle_frame_get(scene, seq));
      /* Offset of -1 is needed because in the sequencer every frame has width.
       * Range from 1 to 1 is drawn as range 1 to 2, because 1 frame long strip starts at frame 1
       * and ends at frame 2. See #106480. */
      efra = max_ii(efra, SEQ_time_right_handle_frame_get(scene, seq) - 1);
    }
  }

  if (!selected) {
    BKE_report(op->reports, RPT_WARNING, "Select one or more strips");
    return OPERATOR_CANCELLED;
  }
  if (efra < 0) {
    BKE_report(op->reports, RPT_ERROR, "Can't set a negative range");
    return OPERATOR_CANCELLED;
  }

  if (preview) {
    scene->r.flag |= SCER_PRV_RANGE;
    scene->r.psfra = max_ii(0, sfra);
    scene->r.pefra = efra;
  }
  else {
    scene->r.flag &= ~SCER_PRV_RANGE;
    scene->r.sfra = max_ii(0, sfra);
    scene->r.efra = efra;
  }

  WM_event_add_notifier(C, NC_SCENE | ND_FRAME_RANGE, scene);

  return OPERATOR_FINISHED;
}

void SEQUENCER_OT_set_range_to_strips(wmOperatorType *ot)
{
  PropertyRNA *prop;

  /* Identifiers. */
  ot->name = "Set Range to Strips";
  ot->idname = "SEQUENCER_OT_set_range_to_strips";
  ot->description = "Set the frame range to the selected strips start and end";

  /* Api callbacks. */
  ot->exec = sequencer_set_range_to_strips_exec;
  ot->poll = sequencer_edit_poll;

  /* Flags. */
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

  prop = RNA_def_boolean(ot->srna, "preview", false, "Preview", "Set the preview range instead");
  RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN);
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Clear Strip Transform Operator
 * \{ */

enum {
  STRIP_TRANSFORM_POSITION,
  STRIP_TRANSFORM_SCALE,
  STRIP_TRANSFORM_ROTATION,
  STRIP_TRANSFORM_ALL,
};

static const EnumPropertyItem transform_reset_properties[] = {
    {STRIP_TRANSFORM_POSITION, "POSITION", 0, "Position", "Reset strip transform location"},
    {STRIP_TRANSFORM_SCALE, "SCALE", 0, "Scale", "Reset strip transform scale"},
    {STRIP_TRANSFORM_ROTATION, "ROTATION", 0, "Rotation", "Reset strip transform rotation"},
    {STRIP_TRANSFORM_ALL, "ALL", 0, "All", "Reset strip transform location, scale and rotation"},
    {0, nullptr, 0, nullptr, nullptr},
};

static int sequencer_strip_transform_clear_exec(bContext *C, wmOperator *op)
{
  Scene *scene = CTX_data_scene(C);
  const Editing *ed = SEQ_editing_get(scene);
  const int property = RNA_enum_get(op->ptr, "property");

  LISTBASE_FOREACH (Sequence *, seq, ed->seqbasep) {
    if (seq->flag & SELECT && seq->type != SEQ_TYPE_SOUND_RAM) {
      StripTransform *transform = seq->strip->transform;
      switch (property) {
        case STRIP_TRANSFORM_POSITION:
          transform->xofs = 0;
          transform->yofs = 0;
          break;
        case STRIP_TRANSFORM_SCALE:
          transform->scale_x = 1.0f;
          transform->scale_y = 1.0f;
          break;
        case STRIP_TRANSFORM_ROTATION:
          transform->rotation = 0.0f;
          break;
        case STRIP_TRANSFORM_ALL:
          transform->xofs = 0;
          transform->yofs = 0;
          transform->scale_x = 1.0f;
          transform->scale_y = 1.0f;
          transform->rotation = 0.0f;
          break;
      }
      SEQ_relations_invalidate_cache_preprocessed(scene, seq);
    }
  }

  WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
  return OPERATOR_FINISHED;
}

void SEQUENCER_OT_strip_transform_clear(wmOperatorType *ot)
{
  /* Identifiers. */
  ot->name = "Clear Strip Transform";
  ot->idname = "SEQUENCER_OT_strip_transform_clear";
  ot->description = "Reset image transformation to default value";

  /* Api callbacks. */
  ot->exec = sequencer_strip_transform_clear_exec;
  ot->poll = sequencer_edit_poll;

  /* Flags. */
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

  ot->prop = RNA_def_enum(ot->srna,
                          "property",
                          transform_reset_properties,
                          STRIP_TRANSFORM_ALL,
                          "Property",
                          "Strip transform property to be reset");
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Transform Set Fit Operator
 * \{ */

static const EnumPropertyItem scale_fit_methods[] = {
    {SEQ_SCALE_TO_FIT, "FIT", 0, "Scale to Fit", "Scale image so fits in preview"},
    {SEQ_SCALE_TO_FILL, "FILL", 0, "Scale to Fill", "Scale image so it fills preview completely"},
    {SEQ_STRETCH_TO_FILL, "STRETCH", 0, "Stretch to Fill", "Stretch image so it fills preview"},
    {0, nullptr, 0, nullptr, nullptr},
};

static int sequencer_strip_transform_fit_exec(bContext *C, wmOperator *op)
{
  Scene *scene = CTX_data_scene(C);
  const Editing *ed = SEQ_editing_get(scene);
  const eSeqImageFitMethod fit_method = eSeqImageFitMethod(RNA_enum_get(op->ptr, "fit_method"));

  LISTBASE_FOREACH (Sequence *, seq, ed->seqbasep) {
    if (seq->flag & SELECT && seq->type != SEQ_TYPE_SOUND_RAM) {
      const int timeline_frame = scene->r.cfra;
      StripElem *strip_elem = SEQ_render_give_stripelem(scene, seq, timeline_frame);

      if (strip_elem == nullptr) {
        continue;
      }

      SEQ_set_scale_to_fit(seq,
                           strip_elem->orig_width,
                           strip_elem->orig_height,
                           scene->r.xsch,
                           scene->r.ysch,
                           fit_method);
      SEQ_relations_invalidate_cache_preprocessed(scene, seq);
    }
  }

  WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
  return OPERATOR_FINISHED;
}

void SEQUENCER_OT_strip_transform_fit(wmOperatorType *ot)
{
  /* Identifiers. */
  ot->name = "Strip Transform Set Fit";
  ot->idname = "SEQUENCER_OT_strip_transform_fit";

  /* Api callbacks. */
  ot->exec = sequencer_strip_transform_fit_exec;
  ot->poll = sequencer_edit_poll;

  /* Flags. */
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

  ot->prop = RNA_def_enum(ot->srna,
                          "fit_method",
                          scale_fit_methods,
                          SEQ_SCALE_TO_FIT,
                          "Fit Method",
                          "Scale fit fit_method");
}

static int sequencer_strip_color_tag_set_exec(bContext *C, wmOperator *op)
{
  Scene *scene = CTX_data_scene(C);
  const Editing *ed = SEQ_editing_get(scene);
  const short color_tag = RNA_enum_get(op->ptr, "color");

  LISTBASE_FOREACH (Sequence *, seq, ed->seqbasep) {
    if (seq->flag & SELECT) {
      seq->color_tag = color_tag;
    }
  }

  WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
  return OPERATOR_FINISHED;
}

static bool sequencer_strip_color_tag_set_poll(bContext *C)
{
  Scene *scene = CTX_data_scene(C);
  if (scene == nullptr) {
    return false;
  }

  Editing *ed = SEQ_editing_get(scene);
  if (ed == nullptr) {
    return false;
  }

  Sequence *act_seq = ed->act_seq;
  return act_seq != nullptr;
}

void SEQUENCER_OT_strip_color_tag_set(wmOperatorType *ot)
{
  /* Identifiers. */
  ot->name = "Set Color Tag";
  ot->idname = "SEQUENCER_OT_strip_color_tag_set";
  ot->description = "Set a color tag for the selected strips";

  /* Api callbacks. */
  ot->exec = sequencer_strip_color_tag_set_exec;
  ot->poll = sequencer_strip_color_tag_set_poll;

  /* Flags. */
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

  RNA_def_enum(
      ot->srna, "color", rna_enum_strip_color_items, SEQUENCE_COLOR_NONE, "Color Tag", "");
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Set 2D Cursor Operator
 * \{ */

static int sequencer_set_2d_cursor_exec(bContext *C, wmOperator *op)
{
  Scene *scene = CTX_data_scene(C);
  SpaceSeq *sseq = CTX_wm_space_seq(C);

  float cursor_pixel[2];
  RNA_float_get_array(op->ptr, "location", cursor_pixel);

  SEQ_image_preview_unit_from_px(scene, cursor_pixel, sseq->cursor);

  WM_event_add_notifier(C, NC_SPACE | ND_SPACE_SEQUENCER, nullptr);

  /* Use pass-through to allow click-drag to transform the cursor. */
  return OPERATOR_FINISHED | OPERATOR_PASS_THROUGH;
}

static int sequencer_set_2d_cursor_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
  ARegion *region = CTX_wm_region(C);
  float cursor_pixel[2];
  UI_view2d_region_to_view(
      &region->v2d, event->mval[0], event->mval[1], &cursor_pixel[0], &cursor_pixel[1]);

  RNA_float_set_array(op->ptr, "location", cursor_pixel);

  return sequencer_set_2d_cursor_exec(C, op);
}

void SEQUENCER_OT_cursor_set(wmOperatorType *ot)
{
  /* identifiers */
  ot->name = "Set 2D Cursor";
  ot->description = "Set 2D cursor location";
  ot->idname = "SEQUENCER_OT_cursor_set";

  /* api callbacks */
  ot->exec = sequencer_set_2d_cursor_exec;
  ot->invoke = sequencer_set_2d_cursor_invoke;
  ot->poll = sequencer_view_has_preview_poll;

  /* flags */
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

  /* properties */
  RNA_def_float_vector(ot->srna,
                       "location",
                       2,
                       nullptr,
                       -FLT_MAX,
                       FLT_MAX,
                       "Location",
                       "Cursor location in normalized preview coordinates",
                       -10.0f,
                       10.0f);
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Update scene strip frame range
 * \{ */

static int sequencer_scene_frame_range_update_exec(bContext *C, wmOperator * /*op*/)
{
  Scene *scene = CTX_data_scene(C);
  Editing *ed = SEQ_editing_get(scene);
  Sequence *seq = ed->act_seq;

  const int old_start = SEQ_time_left_handle_frame_get(scene, seq);
  const int old_end = SEQ_time_right_handle_frame_get(scene, seq);

  Scene *target_scene = seq->scene;

  seq->len = target_scene->r.efra - target_scene->r.sfra + 1;
  SEQ_time_left_handle_frame_set(scene, seq, old_start);
  SEQ_time_right_handle_frame_set(scene, seq, old_end);

  SEQ_relations_invalidate_cache_raw(scene, seq);
  DEG_id_tag_update(&scene->id, ID_RECALC_AUDIO | ID_RECALC_SEQUENCER_STRIPS);
  WM_event_add_notifier(C, NC_SPACE | ND_SPACE_SEQUENCER, nullptr);
  return OPERATOR_FINISHED;
}

static bool sequencer_scene_frame_range_update_poll(bContext *C)
{
  Editing *ed = SEQ_editing_get(CTX_data_scene(C));
  return (ed != nullptr && ed->act_seq != nullptr && (ed->act_seq->type & SEQ_TYPE_SCENE) != 0);
}

void SEQUENCER_OT_scene_frame_range_update(wmOperatorType *ot)
{
  /* identifiers */
  ot->name = "Update Scene Frame Range";
  ot->description = "Update frame range of scene strip";
  ot->idname = "SEQUENCER_OT_scene_frame_range_update";

  /* api callbacks */
  ot->exec = sequencer_scene_frame_range_update_exec;
  ot->poll = sequencer_scene_frame_range_update_poll;

  /* flags */
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}

/** \} */
